diff options
| author | Rob Mensching <rob@firegiant.com> | 2020-07-05 23:26:48 -0700 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2020-07-08 15:18:09 -0700 |
| commit | b8bd03960b79e92d38ee7094a88e246253dad800 (patch) | |
| tree | 89fec2cf4003832110bcbdd0633c8dcd492efd54 /src | |
| parent | 7b583330fd42356930bdc5a28820e546f6ca45a4 (diff) | |
| download | wix-b8bd03960b79e92d38ee7094a88e246253dad800.tar.gz wix-b8bd03960b79e92d38ee7094a88e246253dad800.tar.bz2 wix-b8bd03960b79e92d38ee7094a88e246253dad800.zip | |
Improve compiler performance by removing regex and other string fixes
Diffstat (limited to 'src')
| -rw-r--r-- | src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs | 11 | ||||
| -rw-r--r-- | src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs | 73 | ||||
| -rw-r--r-- | src/WixToolset.Core/Common.cs | 271 | ||||
| -rw-r--r-- | src/WixToolset.Core/Compiler.cs | 47 | ||||
| -rw-r--r-- | src/WixToolset.Core/CompilerCore.cs | 55 | ||||
| -rw-r--r-- | src/WixToolset.Core/Compiler_2.cs | 28 | ||||
| -rw-r--r-- | src/WixToolset.Core/Compiler_Bundle.cs | 2 | ||||
| -rw-r--r-- | src/WixToolset.Core/Compiler_UI.cs | 2 | ||||
| -rw-r--r-- | src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs | 132 | ||||
| -rw-r--r-- | src/WixToolset.Core/ParsedWixVariable.cs | 19 | ||||
| -rw-r--r-- | src/WixToolset.Core/VariableResolver.cs | 188 |
11 files changed, 454 insertions, 374 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs index d5601fad..36172b5e 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs | |||
| @@ -226,11 +226,12 @@ namespace WixToolset.Core.WindowsInstaller.Unbind | |||
| 226 | value = value.Replace("$(", "$$("); | 226 | value = value.Replace("$(", "$$("); |
| 227 | 227 | ||
| 228 | // escape things that look like wix variables | 228 | // escape things that look like wix variables |
| 229 | var matches = Common.WixVariableRegex.Matches(value); | 229 | // TODO: Evaluate this requirement. |
| 230 | for (var j = matches.Count - 1; 0 <= j; j--) | 230 | //var matches = Common.WixVariableRegex.Matches(value); |
| 231 | { | 231 | //for (var j = matches.Count - 1; 0 <= j; j--) |
| 232 | value = value.Insert(matches[j].Index, "!"); | 232 | //{ |
| 233 | } | 233 | // value = value.Insert(matches[j].Index, "!"); |
| 234 | //} | ||
| 234 | 235 | ||
| 235 | row[i] = value; | 236 | row[i] = value; |
| 236 | break; | 237 | break; |
diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs index a10b98dc..ebabed47 100644 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs | |||
| @@ -69,11 +69,11 @@ namespace WixToolset.Core.Bind | |||
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | // add specialization for ProductVersion fields | 71 | // add specialization for ProductVersion fields |
| 72 | string keyProductVersion = "property.ProductVersion"; | 72 | var keyProductVersion = "property.ProductVersion"; |
| 73 | if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out Version productVersion)) | 73 | if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out Version productVersion)) |
| 74 | { | 74 | { |
| 75 | // Don't add the variable if it already exists (developer defined a property with the same name). | 75 | // Don't add the variable if it already exists (developer defined a property with the same name). |
| 76 | string fieldKey = String.Concat(keyProductVersion, ".Major"); | 76 | var fieldKey = String.Concat(keyProductVersion, ".Major"); |
| 77 | if (!this.VariableCache.ContainsKey(fieldKey)) | 77 | if (!this.VariableCache.ContainsKey(fieldKey)) |
| 78 | { | 78 | { |
| 79 | this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); | 79 | this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); |
| @@ -115,68 +115,45 @@ namespace WixToolset.Core.Bind | |||
| 115 | 115 | ||
| 116 | private static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData) | 116 | private static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData) |
| 117 | { | 117 | { |
| 118 | var matches = Common.WixVariableRegex.Matches(value); | 118 | var start = 0; |
| 119 | 119 | ||
| 120 | if (0 < matches.Count) | 120 | while (Common.TryParseWixVariable(value, start, out var parsed)) |
| 121 | { | 121 | { |
| 122 | var sb = new StringBuilder(value); | 122 | if (parsed.Namespace == "bind") |
| 123 | |||
| 124 | // notice how this code walks backward through the list | ||
| 125 | // because it modifies the string as we go through it | ||
| 126 | for (int i = matches.Count - 1; 0 <= i; i--) | ||
| 127 | { | 123 | { |
| 128 | string variableNamespace = matches[i].Groups["namespace"].Value; | 124 | var key = String.Concat(parsed.Name, ".", parsed.Scope); |
| 129 | string variableId = matches[i].Groups["fullname"].Value; | ||
| 130 | string variableDefaultValue = null; | ||
| 131 | string variableScope = null; | ||
| 132 | 125 | ||
| 133 | // get the default value if one was specified | 126 | if (!resolutionData.TryGetValue(key, out var resolvedValue)) |
| 134 | if (matches[i].Groups["value"].Success) | ||
| 135 | { | 127 | { |
| 136 | variableDefaultValue = matches[i].Groups["value"].Value; | 128 | resolvedValue = parsed.DefaultValue; |
| 137 | } | 129 | } |
| 138 | 130 | ||
| 139 | // get the scope if one was specified | 131 | // insert the resolved value if it was found or display an error |
| 140 | if (matches[i].Groups["scope"].Success) | 132 | if (null != resolvedValue) |
| 141 | { | 133 | { |
| 142 | variableScope = matches[i].Groups["scope"].Value; | 134 | if (parsed.Index == 0 && parsed.Length == value.Length) |
| 143 | if ("bind" == variableNamespace) | ||
| 144 | { | 135 | { |
| 145 | variableId = matches[i].Groups["name"].Value; | 136 | value = resolvedValue; |
| 137 | } | ||
| 138 | else | ||
| 139 | { | ||
| 140 | var sb = new StringBuilder(value); | ||
| 141 | sb.Remove(parsed.Index, parsed.Length); | ||
| 142 | sb.Insert(parsed.Index, resolvedValue); | ||
| 143 | value = sb.ToString(); | ||
| 146 | } | 144 | } |
| 147 | } | ||
| 148 | 145 | ||
| 149 | // check for an escape sequence of !! indicating the match is not a variable expression | 146 | start = parsed.Index; |
| 150 | if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1]) | ||
| 151 | { | ||
| 152 | sb.Remove(matches[i].Index - 1, 1); | ||
| 153 | } | 147 | } |
| 154 | else | 148 | else |
| 155 | { | 149 | { |
| 156 | var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture); | 150 | throw new WixException(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); |
| 157 | |||
| 158 | if (!resolutionData.TryGetValue(key, out var resolvedValue)) | ||
| 159 | { | ||
| 160 | resolvedValue = variableDefaultValue; | ||
| 161 | } | ||
| 162 | |||
| 163 | if ("bind" == variableNamespace) | ||
| 164 | { | ||
| 165 | // insert the resolved value if it was found or display an error | ||
| 166 | if (null != resolvedValue) | ||
| 167 | { | ||
| 168 | sb.Remove(matches[i].Index, matches[i].Length); | ||
| 169 | sb.Insert(matches[i].Index, resolvedValue); | ||
| 170 | } | ||
| 171 | else | ||
| 172 | { | ||
| 173 | throw new WixException(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | } | 151 | } |
| 177 | } | 152 | } |
| 178 | 153 | else | |
| 179 | value = sb.ToString(); | 154 | { |
| 155 | start = parsed.Index + parsed.Length; | ||
| 156 | } | ||
| 180 | } | 157 | } |
| 181 | 158 | ||
| 182 | return value; | 159 | return value; |
diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index a9fc9538..19c77368 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs | |||
| @@ -9,7 +9,6 @@ namespace WixToolset.Core | |||
| 9 | using System.Linq; | 9 | using System.Linq; |
| 10 | using System.Security.Cryptography; | 10 | using System.Security.Cryptography; |
| 11 | using System.Text; | 11 | using System.Text; |
| 12 | using System.Text.RegularExpressions; | ||
| 13 | using System.Xml; | 12 | using System.Xml; |
| 14 | using System.Xml.Linq; | 13 | using System.Xml.Linq; |
| 15 | using WixToolset.Data; | 14 | using WixToolset.Data; |
| @@ -102,18 +101,14 @@ namespace WixToolset.Core | |||
| 102 | // TODO: Find a place to put this that it doesn't have to be public and exposed by WixToolset.Core.dll | 101 | // TODO: Find a place to put this that it doesn't have to be public and exposed by WixToolset.Core.dll |
| 103 | public static readonly string[] FilePermissions = { "Read", "Write", "Append", "ReadExtendedAttributes", "WriteExtendedAttributes", "Execute", "FileAllRights", "ReadAttributes", "WriteAttributes" }; | 102 | public static readonly string[] FilePermissions = { "Read", "Write", "Append", "ReadExtendedAttributes", "WriteExtendedAttributes", "Execute", "FileAllRights", "ReadAttributes", "WriteAttributes" }; |
| 104 | 103 | ||
| 105 | public static readonly Regex WixVariableRegex = new Regex(@"(\!|\$)\((?<namespace>loc|wix|bind|bindpath)\.(?<fullname>(?<name>[_A-Za-z][0-9A-Za-z_]+)(\.(?<scope>[_A-Za-z][0-9A-Za-z_\.]*))?)(\=(?<value>.+?))?\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); | 104 | internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * " |
| 105 | internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/' | ||
| 106 | internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?' | ||
| 106 | 107 | ||
| 107 | private static readonly Regex PropertySearch = new Regex(@"\[[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*]", RegexOptions.Singleline); | 108 | private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; |
| 108 | private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled); | 109 | private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; |
| 109 | private static readonly Regex LegalIdentifierCharacters = new Regex(@"^[_A-Za-z][0-9A-Za-z_\.]*$", RegexOptions.Compiled); | ||
| 110 | private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters | ||
| 111 | 110 | ||
| 112 | private const string LegalShortFilenameCharacters = @"[^\\\?|><:/\*""\+,;=\[\]\. ]"; // illegal: \ ? | > < : / * " + , ; = [ ] . (space) | ||
| 113 | private static readonly Regex LegalShortFilename = new Regex(String.Concat("^", LegalShortFilenameCharacters, @"{1,8}(\.", LegalShortFilenameCharacters, "{0,3})?$"), RegexOptions.Compiled); | ||
| 114 | 111 | ||
| 115 | private const string LegalWildcardShortFilenameCharacters = @"[^\\|><:/""\+,;=\[\]\. ]"; // illegal: \ | > < : / " + , ; = [ ] . (space) | ||
| 116 | private static readonly Regex LegalWildcardShortFilename = new Regex(String.Concat("^", LegalWildcardShortFilenameCharacters, @"{1,16}(\.", LegalWildcardShortFilenameCharacters, "{0,6})?$")); | ||
| 117 | 112 | ||
| 118 | /// <summary> | 113 | /// <summary> |
| 119 | /// Gets a valid code page from the given web name or integer value. | 114 | /// Gets a valid code page from the given web name or integer value. |
| @@ -197,79 +192,95 @@ namespace WixToolset.Core | |||
| 197 | 192 | ||
| 198 | if (allowWildcards) | 193 | if (allowWildcards) |
| 199 | { | 194 | { |
| 200 | if (Common.LegalWildcardShortFilename.IsMatch(filename)) | 195 | var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters); |
| 196 | if (expectedDot == -1) | ||
| 201 | { | 197 | { |
| 202 | bool foundPeriod = false; | 198 | } |
| 203 | int beforePeriod = 0; | 199 | else if (filename[expectedDot] != '.') |
| 204 | int afterPeriod = 0; | 200 | { |
| 201 | return false; | ||
| 202 | } | ||
| 203 | else if (expectedDot < filename.Length) | ||
| 204 | { | ||
| 205 | var extensionInvalids = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1); | ||
| 206 | if (extensionInvalids != -1) | ||
| 207 | { | ||
| 208 | return false; | ||
| 209 | } | ||
| 210 | } | ||
| 205 | 211 | ||
| 206 | // count the number of characters before and after the period | 212 | var foundPeriod = false; |
| 207 | // '*' is not counted because it may represent zero characters | 213 | var beforePeriod = 0; |
| 208 | foreach (char character in filename) | 214 | var afterPeriod = 0; |
| 215 | |||
| 216 | // count the number of characters before and after the period | ||
| 217 | // '*' is not counted because it may represent zero characters | ||
| 218 | foreach (var character in filename) | ||
| 219 | { | ||
| 220 | if ('.' == character) | ||
| 221 | { | ||
| 222 | foundPeriod = true; | ||
| 223 | } | ||
| 224 | else if ('*' != character) | ||
| 209 | { | 225 | { |
| 210 | if ('.' == character) | 226 | if (foundPeriod) |
| 211 | { | 227 | { |
| 212 | foundPeriod = true; | 228 | afterPeriod++; |
| 213 | } | 229 | } |
| 214 | else if ('*' != character) | 230 | else |
| 215 | { | 231 | { |
| 216 | if (foundPeriod) | 232 | beforePeriod++; |
| 217 | { | ||
| 218 | afterPeriod++; | ||
| 219 | } | ||
| 220 | else | ||
| 221 | { | ||
| 222 | beforePeriod++; | ||
| 223 | } | ||
| 224 | } | 233 | } |
| 225 | } | 234 | } |
| 235 | } | ||
| 226 | 236 | ||
| 227 | if (8 >= beforePeriod && 3 >= afterPeriod) | 237 | if (8 >= beforePeriod && 3 >= afterPeriod) |
| 228 | { | 238 | { |
| 229 | return true; | 239 | return true; |
| 230 | } | ||
| 231 | } | 240 | } |
| 232 | 241 | ||
| 233 | return false; | 242 | return false; |
| 234 | } | 243 | } |
| 235 | else | 244 | else |
| 236 | { | 245 | { |
| 237 | return Common.LegalShortFilename.IsMatch(filename); | 246 | if (filename.Length > 12) |
| 247 | { | ||
| 248 | return false; | ||
| 249 | } | ||
| 250 | |||
| 251 | var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters); | ||
| 252 | if (expectedDot == -1) | ||
| 253 | { | ||
| 254 | return filename.Length < 9; | ||
| 255 | } | ||
| 256 | else if (expectedDot > 8 || filename[expectedDot] != '.') | ||
| 257 | { | ||
| 258 | return false; | ||
| 259 | } | ||
| 260 | |||
| 261 | var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1); | ||
| 262 | return validExtension == -1; | ||
| 238 | } | 263 | } |
| 239 | } | 264 | } |
| 240 | 265 | ||
| 241 | /// <summary> | 266 | /// <summary> |
| 242 | /// Verifies if an identifier is a valid binder variable name. | 267 | /// Verifies if an identifier is a valid binder variable name. |
| 243 | /// </summary> | 268 | /// </summary> |
| 244 | /// <param name="name">Binder variable name to verify.</param> | 269 | /// <param name="variable">Binder variable name to verify.</param> |
| 245 | /// <returns>True if the identifier is a valid binder variable name.</returns> | 270 | /// <returns>True if the identifier is a valid binder variable name.</returns> |
| 246 | public static bool IsValidBinderVariable(string name) | 271 | public static bool IsValidBinderVariable(string variable) |
| 247 | { | 272 | { |
| 248 | if (String.IsNullOrEmpty(name)) | 273 | return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); |
| 249 | { | ||
| 250 | return false; | ||
| 251 | } | ||
| 252 | |||
| 253 | Match match = Common.WixVariableRegex.Match(name); | ||
| 254 | |||
| 255 | return (match.Success && ("bind" == match.Groups["namespace"].Value || "wix" == match.Groups["namespace"].Value) && 0 == match.Index && name.Length == match.Length); | ||
| 256 | } | 274 | } |
| 257 | 275 | ||
| 258 | /// <summary> | 276 | /// <summary> |
| 259 | /// Verifies if a string contains a valid binder variable name. | 277 | /// Verifies if a string contains a valid binder variable name. |
| 260 | /// </summary> | 278 | /// </summary> |
| 261 | /// <param name="name">String to verify.</param> | 279 | /// <param name="verify">String to verify.</param> |
| 262 | /// <returns>True if the string contains a valid binder variable name.</returns> | 280 | /// <returns>True if the string contains a valid binder variable name.</returns> |
| 263 | public static bool ContainsValidBinderVariable(string name) | 281 | public static bool ContainsValidBinderVariable(string verify) |
| 264 | { | 282 | { |
| 265 | if (String.IsNullOrEmpty(name)) | 283 | return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); |
| 266 | { | ||
| 267 | return false; | ||
| 268 | } | ||
| 269 | |||
| 270 | Match match = Common.WixVariableRegex.Match(name); | ||
| 271 | |||
| 272 | return match.Success && ("bind" == match.Groups["namespace"].Value || "wix" == match.Groups["namespace"].Value); | ||
| 273 | } | 284 | } |
| 274 | 285 | ||
| 275 | /// <summary> | 286 | /// <summary> |
| @@ -281,7 +292,7 @@ namespace WixToolset.Core | |||
| 281 | { | 292 | { |
| 282 | if (!Common.IsValidBinderVariable(version)) | 293 | if (!Common.IsValidBinderVariable(version)) |
| 283 | { | 294 | { |
| 284 | Version ver = null; | 295 | Version ver; |
| 285 | 296 | ||
| 286 | try | 297 | try |
| 287 | { | 298 | { |
| @@ -344,16 +355,31 @@ namespace WixToolset.Core | |||
| 344 | /// <returns>A version of the name that is a legal identifier.</returns> | 355 | /// <returns>A version of the name that is a legal identifier.</returns> |
| 345 | internal static string GetIdentifierFromName(string name) | 356 | internal static string GetIdentifierFromName(string name) |
| 346 | { | 357 | { |
| 347 | string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_". | 358 | StringBuilder sb = null; |
| 359 | var offset = 0; | ||
| 348 | 360 | ||
| 349 | // MSI identifiers must begin with an alphabetic character or an | 361 | // MSI identifiers must begin with an alphabetic character or an |
| 350 | // underscore. Prefix all other values with an underscore. | 362 | // underscore. Prefix all other values with an underscore. |
| 351 | if (AddPrefix.IsMatch(name)) | 363 | if (!ValidIdentifierChar(name[0], true)) |
| 352 | { | 364 | { |
| 353 | result = String.Concat("_", result); | 365 | sb = new StringBuilder("_" + name); |
| 366 | offset = 1; | ||
| 354 | } | 367 | } |
| 355 | 368 | ||
| 356 | return result; | 369 | for (var i = 0; i < name.Length; ++i) |
| 370 | { | ||
| 371 | if (!ValidIdentifierChar(name[i], false)) | ||
| 372 | { | ||
| 373 | if (sb == null) | ||
| 374 | { | ||
| 375 | sb = new StringBuilder(name); | ||
| 376 | } | ||
| 377 | |||
| 378 | sb[i + offset] = '_'; | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | return sb?.ToString() ?? name; | ||
| 357 | } | 383 | } |
| 358 | 384 | ||
| 359 | /// <summary> | 385 | /// <summary> |
| @@ -363,7 +389,28 @@ namespace WixToolset.Core | |||
| 363 | /// <returns>True if a property is found in the string.</returns> | 389 | /// <returns>True if a property is found in the string.</returns> |
| 364 | internal static bool ContainsProperty(string possibleProperty) | 390 | internal static bool ContainsProperty(string possibleProperty) |
| 365 | { | 391 | { |
| 366 | return PropertySearch.IsMatch(possibleProperty); | 392 | var start = possibleProperty.IndexOf('['); |
| 393 | if (start != -1 && start < possibleProperty.Length - 2) | ||
| 394 | { | ||
| 395 | var end = possibleProperty.IndexOf(']', start + 1); | ||
| 396 | if (end > start + 1) | ||
| 397 | { | ||
| 398 | // Skip supported property modifiers. | ||
| 399 | if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!') | ||
| 400 | { | ||
| 401 | ++start; | ||
| 402 | } | ||
| 403 | |||
| 404 | var id = possibleProperty.Substring(start + 1, end - 1); | ||
| 405 | |||
| 406 | if (Common.IsIdentifier(id)) | ||
| 407 | { | ||
| 408 | return true; | ||
| 409 | } | ||
| 410 | } | ||
| 411 | } | ||
| 412 | |||
| 413 | return false; | ||
| 367 | } | 414 | } |
| 368 | 415 | ||
| 369 | /// <summary> | 416 | /// <summary> |
| @@ -436,7 +483,7 @@ namespace WixToolset.Core | |||
| 436 | public static string[] GetNames(string value) | 483 | public static string[] GetNames(string value) |
| 437 | { | 484 | { |
| 438 | string[] names = new string[4]; | 485 | string[] names = new string[4]; |
| 439 | int targetSeparator = value.IndexOf(":", StringComparison.Ordinal); | 486 | int targetSeparator = value.IndexOf(':'); |
| 440 | 487 | ||
| 441 | // split source and target | 488 | // split source and target |
| 442 | string sourceName = null; | 489 | string sourceName = null; |
| @@ -451,7 +498,7 @@ namespace WixToolset.Core | |||
| 451 | string sourceLongName = null; | 498 | string sourceLongName = null; |
| 452 | if (null != sourceName) | 499 | if (null != sourceName) |
| 453 | { | 500 | { |
| 454 | int sourceLongNameSeparator = sourceName.IndexOf("|", StringComparison.Ordinal); | 501 | int sourceLongNameSeparator = sourceName.IndexOf('|'); |
| 455 | if (0 <= sourceLongNameSeparator) | 502 | if (0 <= sourceLongNameSeparator) |
| 456 | { | 503 | { |
| 457 | sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); | 504 | sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); |
| @@ -460,7 +507,7 @@ namespace WixToolset.Core | |||
| 460 | } | 507 | } |
| 461 | 508 | ||
| 462 | // split the target short and long names | 509 | // split the target short and long names |
| 463 | int targetLongNameSeparator = targetName.IndexOf("|", StringComparison.Ordinal); | 510 | int targetLongNameSeparator = targetName.IndexOf('|'); |
| 464 | string targetLongName = null; | 511 | string targetLongName = null; |
| 465 | if (0 <= targetLongNameSeparator) | 512 | if (0 <= targetLongNameSeparator) |
| 466 | { | 513 | { |
| @@ -555,7 +602,7 @@ namespace WixToolset.Core | |||
| 555 | /// <returns>The attribute's value.</returns> | 602 | /// <returns>The attribute's value.</returns> |
| 556 | internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule) | 603 | internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule) |
| 557 | { | 604 | { |
| 558 | string value = attribute.Value; | 605 | var value = attribute.Value; |
| 559 | 606 | ||
| 560 | if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) || | 607 | if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) || |
| 561 | (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value))) | 608 | (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value))) |
| @@ -574,15 +621,20 @@ namespace WixToolset.Core | |||
| 574 | /// <returns>true if the value is an identifier; false otherwise.</returns> | 621 | /// <returns>true if the value is an identifier; false otherwise.</returns> |
| 575 | public static bool IsIdentifier(string value) | 622 | public static bool IsIdentifier(string value) |
| 576 | { | 623 | { |
| 577 | if (!String.IsNullOrEmpty(value)) | 624 | if (String.IsNullOrEmpty(value)) |
| 625 | { | ||
| 626 | return false; | ||
| 627 | } | ||
| 628 | |||
| 629 | for (var i = 0; i < value.Length; ++i) | ||
| 578 | { | 630 | { |
| 579 | if (LegalIdentifierCharacters.IsMatch(value)) | 631 | if (!ValidIdentifierChar(value[i], i == 0)) |
| 580 | { | 632 | { |
| 581 | return true; | 633 | return false; |
| 582 | } | 634 | } |
| 583 | } | 635 | } |
| 584 | 636 | ||
| 585 | return false; | 637 | return true; |
| 586 | } | 638 | } |
| 587 | 639 | ||
| 588 | /// <summary> | 640 | /// <summary> |
| @@ -698,6 +750,85 @@ namespace WixToolset.Core | |||
| 698 | return text?.Value; | 750 | return text?.Value; |
| 699 | } | 751 | } |
| 700 | 752 | ||
| 753 | internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable) | ||
| 754 | { | ||
| 755 | parsedVariable = null; | ||
| 756 | |||
| 757 | if (String.IsNullOrEmpty(value) || start >= value.Length) | ||
| 758 | { | ||
| 759 | return false; | ||
| 760 | } | ||
| 761 | |||
| 762 | var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal); | ||
| 763 | if (startWixVariable == -1) | ||
| 764 | { | ||
| 765 | return false; | ||
| 766 | } | ||
| 767 | |||
| 768 | var firstDot = value.IndexOf('.', startWixVariable + 1); | ||
| 769 | if (firstDot == -1) | ||
| 770 | { | ||
| 771 | return false; | ||
| 772 | } | ||
| 773 | |||
| 774 | var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2); | ||
| 775 | if (ns != "loc" && ns != "bind" && ns != "wix") | ||
| 776 | { | ||
| 777 | return false; | ||
| 778 | } | ||
| 779 | |||
| 780 | var closeParen = value.IndexOf(')', firstDot); | ||
| 781 | if (closeParen == -1) | ||
| 782 | { | ||
| 783 | return false; | ||
| 784 | } | ||
| 785 | |||
| 786 | string name; | ||
| 787 | string scope = null; | ||
| 788 | string defaultValue = null; | ||
| 789 | |||
| 790 | var secondDot = value.IndexOf('.', firstDot + 1, closeParen - firstDot); | ||
| 791 | var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot); | ||
| 792 | var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue; | ||
| 793 | |||
| 794 | if (secondDot == -1) | ||
| 795 | { | ||
| 796 | name = value.Substring(firstDot + 1, end - firstDot - 1); | ||
| 797 | } | ||
| 798 | else | ||
| 799 | { | ||
| 800 | name = value.Substring(firstDot + 1, secondDot - firstDot - 1); | ||
| 801 | scope = value.Substring(secondDot + 1, end - secondDot - 1); | ||
| 802 | |||
| 803 | if (!Common.IsIdentifier(scope)) | ||
| 804 | { | ||
| 805 | return false; | ||
| 806 | } | ||
| 807 | } | ||
| 808 | |||
| 809 | if (!Common.IsIdentifier(name)) | ||
| 810 | { | ||
| 811 | return false; | ||
| 812 | } | ||
| 813 | |||
| 814 | if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen) | ||
| 815 | { | ||
| 816 | defaultValue = value.Substring(equalsDefaultValue + 1, end - equalsDefaultValue - 1); | ||
| 817 | } | ||
| 818 | |||
| 819 | parsedVariable = new ParsedWixVariable | ||
| 820 | { | ||
| 821 | Index = startWixVariable, | ||
| 822 | Length = closeParen - startWixVariable + 1, | ||
| 823 | Namespace = ns, | ||
| 824 | Name = name, | ||
| 825 | Scope = scope, | ||
| 826 | DefaultValue = defaultValue | ||
| 827 | }; | ||
| 828 | |||
| 829 | return true; | ||
| 830 | } | ||
| 831 | |||
| 701 | /// <summary> | 832 | /// <summary> |
| 702 | /// Display an unexpected attribute error. | 833 | /// Display an unexpected attribute error. |
| 703 | /// </summary> | 834 | /// </summary> |
| @@ -727,5 +858,11 @@ namespace WixToolset.Core | |||
| 727 | messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName)); | 858 | messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName)); |
| 728 | } | 859 | } |
| 729 | } | 860 | } |
| 861 | |||
| 862 | private static bool ValidIdentifierChar(char c, bool firstChar) | ||
| 863 | { | ||
| 864 | return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c || | ||
| 865 | (!firstChar && (Char.IsDigit(c) || '.' == c)); | ||
| 866 | } | ||
| 730 | } | 867 | } |
| 731 | } | 868 | } |
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index b29821b0..020e35fe 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs | |||
| @@ -9,7 +9,6 @@ namespace WixToolset.Core | |||
| 9 | using System.Globalization; | 9 | using System.Globalization; |
| 10 | using System.IO; | 10 | using System.IO; |
| 11 | using System.Linq; | 11 | using System.Linq; |
| 12 | using System.Text.RegularExpressions; | ||
| 13 | using System.Xml.Linq; | 12 | using System.Xml.Linq; |
| 14 | using WixToolset.Data; | 13 | using WixToolset.Data; |
| 15 | using WixToolset.Data.Symbols; | 14 | using WixToolset.Data.Symbols; |
| @@ -26,8 +25,8 @@ namespace WixToolset.Core | |||
| 26 | private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB | 25 | private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB |
| 27 | private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) | 26 | private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) |
| 28 | 27 | ||
| 29 | private const string DefaultComponentIdPlaceholderFormat = "WixComponentIdPlaceholder{0}"; | 28 | private const string DefaultComponentIdPlaceholderPrefix = "WixComponentIdPlaceholder"; |
| 30 | private const string DefaultComponentIdPlaceholderWixVariableFormat = "!(wix.{0})"; | 29 | private const string DefaultComponentIdPlaceholderWixVariablePrefix = "!(wix."; |
| 31 | // If these are true you know you are building a module or product | 30 | // If these are true you know you are building a module or product |
| 32 | // but if they are false you cannot not be sure they will not end | 31 | // but if they are false you cannot not be sure they will not end |
| 33 | // up a product or module. Use these flags carefully. | 32 | // up a product or module. Use these flags carefully. |
| @@ -342,16 +341,22 @@ namespace WixToolset.Core | |||
| 342 | 341 | ||
| 343 | if (!String.IsNullOrEmpty(value)) | 342 | if (!String.IsNullOrEmpty(value)) |
| 344 | { | 343 | { |
| 345 | var regex = new Regex(@"\[(?<identifier>[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); | 344 | var start = value.IndexOf('['); |
| 346 | var matches = regex.Matches(value); | 345 | while (start != -1 && start < value.Length) |
| 347 | |||
| 348 | foreach (Match match in matches) | ||
| 349 | { | 346 | { |
| 350 | var group = match.Groups["identifier"]; | 347 | var end = value.IndexOf(']', start + 1); |
| 351 | if (group.Success) | 348 | if (end == -1) |
| 349 | { | ||
| 350 | break; | ||
| 351 | } | ||
| 352 | |||
| 353 | var id = value.Substring(start + 1, end - 1); | ||
| 354 | if (Common.IsIdentifier(id)) | ||
| 352 | { | 355 | { |
| 353 | this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, group.Value)); | 356 | this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id)); |
| 354 | } | 357 | } |
| 358 | |||
| 359 | start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1; | ||
| 355 | } | 360 | } |
| 356 | } | 361 | } |
| 357 | 362 | ||
| @@ -2091,8 +2096,8 @@ namespace WixToolset.Core | |||
| 2091 | var encounteredODBCDataSource = false; | 2096 | var encounteredODBCDataSource = false; |
| 2092 | var files = 0; | 2097 | var files = 0; |
| 2093 | var guid = "*"; | 2098 | var guid = "*"; |
| 2094 | var componentIdPlaceholder = String.Format(Compiler.DefaultComponentIdPlaceholderFormat, this.componentIdPlaceholdersResolver.VariableCount); // placeholder id for defaulting Component/@Id to keypath id. | 2099 | var componentIdPlaceholder = Compiler.DefaultComponentIdPlaceholderPrefix + this.componentIdPlaceholdersResolver.VariableCount; // placeholder id for defaulting Component/@Id to keypath id. |
| 2095 | var componentIdPlaceholderWixVariable = String.Format(Compiler.DefaultComponentIdPlaceholderWixVariableFormat, componentIdPlaceholder); | 2100 | var componentIdPlaceholderWixVariable = Compiler.DefaultComponentIdPlaceholderWixVariablePrefix + componentIdPlaceholder + ")"; |
| 2096 | var id = new Identifier(AccessModifier.Private, componentIdPlaceholderWixVariable); | 2101 | var id = new Identifier(AccessModifier.Private, componentIdPlaceholderWixVariable); |
| 2097 | var keyFound = false; | 2102 | var keyFound = false; |
| 2098 | string keyPath = null; | 2103 | string keyPath = null; |
| @@ -4080,7 +4085,7 @@ namespace WixToolset.Core | |||
| 4080 | break; | 4085 | break; |
| 4081 | case "Name": | 4086 | case "Name": |
| 4082 | nameHasValue = true; | 4087 | nameHasValue = true; |
| 4083 | if (attrib.Value.Equals(".")) | 4088 | if (attrib.Value == ".") |
| 4084 | { | 4089 | { |
| 4085 | name = attrib.Value; | 4090 | name = attrib.Value; |
| 4086 | } | 4091 | } |
| @@ -4225,7 +4230,7 @@ namespace WixToolset.Core | |||
| 4225 | defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName)); | 4230 | defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName)); |
| 4226 | } | 4231 | } |
| 4227 | 4232 | ||
| 4228 | if ("TARGETDIR".Equals(id.Id) && !"SourceDir".Equals(defaultDir)) | 4233 | if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !"SourceDir".Equals(defaultDir, StringComparison.Ordinal)) |
| 4229 | { | 4234 | { |
| 4230 | this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, defaultDir)); | 4235 | this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, defaultDir)); |
| 4231 | } | 4236 | } |
| @@ -7147,13 +7152,9 @@ namespace WixToolset.Core | |||
| 7147 | else // external cabinet file | 7152 | else // external cabinet file |
| 7148 | { | 7153 | { |
| 7149 | // external cabinet files must use 8.3 filenames | 7154 | // external cabinet files must use 8.3 filenames |
| 7150 | if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidShortFilename(cabinet, false)) | 7155 | if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet)) |
| 7151 | { | 7156 | { |
| 7152 | // WiX variables in the name will trip the "not a valid 8.3 name" switch, so let them through | 7157 | this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); |
| 7153 | if (!Common.WixVariableRegex.Match(cabinet).Success) | ||
| 7154 | { | ||
| 7155 | this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); | ||
| 7156 | } | ||
| 7157 | } | 7158 | } |
| 7158 | } | 7159 | } |
| 7159 | } | 7160 | } |
| @@ -7286,12 +7287,12 @@ namespace WixToolset.Core | |||
| 7286 | if (!this.Core.IsValidLocIdentifier(exampleCabinetName)) | 7287 | if (!this.Core.IsValidLocIdentifier(exampleCabinetName)) |
| 7287 | { | 7288 | { |
| 7288 | // The example name should not match the authored template since that would nullify the | 7289 | // The example name should not match the authored template since that would nullify the |
| 7289 | // reason for having multiple cabients. External cabinet files must also be valid file names. | 7290 | // reason for having multiple cabinets. External cabinet files must also be valid file names. |
| 7290 | if (exampleCabinetName.Equals(authoredCabinetTemplateValue) || !this.Core.IsValidLongFilename(exampleCabinetName, false)) | 7291 | if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false)) |
| 7291 | { | 7292 | { |
| 7292 | this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); | 7293 | this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); |
| 7293 | } | 7294 | } |
| 7294 | else if (!this.Core.IsValidShortFilename(exampleCabinetName, false) && !Common.WixVariableRegex.Match(exampleCabinetName).Success) // ignore short names with wix variables because it rarely works out. | 7295 | else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out. |
| 7295 | { | 7296 | { |
| 7296 | this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); | 7297 | this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); |
| 7297 | } | 7298 | } |
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index c2724f6b..2f65db7a 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs | |||
| @@ -10,7 +10,6 @@ namespace WixToolset.Core | |||
| 10 | using System.Globalization; | 10 | using System.Globalization; |
| 11 | using System.Reflection; | 11 | using System.Reflection; |
| 12 | using System.Text; | 12 | using System.Text; |
| 13 | using System.Text.RegularExpressions; | ||
| 14 | using System.Xml.Linq; | 13 | using System.Xml.Linq; |
| 15 | using WixToolset.Data; | 14 | using WixToolset.Data; |
| 16 | using WixToolset.Data.Symbols; | 15 | using WixToolset.Data.Symbols; |
| @@ -45,16 +44,8 @@ namespace WixToolset.Core | |||
| 45 | internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; | 44 | internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; |
| 46 | internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; | 45 | internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; |
| 47 | 46 | ||
| 48 | private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled); | ||
| 49 | |||
| 50 | private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * " | ||
| 51 | private static readonly Regex IllegalLongFilename = new Regex(IllegalLongFilenameCharacters, RegexOptions.Compiled); | ||
| 52 | |||
| 53 | //public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB | ||
| 54 | |||
| 55 | |||
| 56 | // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) | 47 | // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) |
| 57 | private static readonly List<String> BuiltinBundleVariables = new List<string>( | 48 | private static readonly List<string> BuiltinBundleVariables = new List<string>( |
| 58 | new string[] { | 49 | new string[] { |
| 59 | "AdminToolsFolder", | 50 | "AdminToolsFolder", |
| 60 | "AppDataFolder", | 51 | "AppDataFolder", |
| @@ -221,7 +212,13 @@ namespace WixToolset.Core | |||
| 221 | /// <returns>true if the filename is ambiguous; false otherwise.</returns> | 212 | /// <returns>true if the filename is ambiguous; false otherwise.</returns> |
| 222 | public static bool IsAmbiguousFilename(string filename) | 213 | public static bool IsAmbiguousFilename(string filename) |
| 223 | { | 214 | { |
| 224 | return String.IsNullOrEmpty(filename) ? false : CompilerCore.AmbiguousFilename.IsMatch(filename); | 215 | if (String.IsNullOrEmpty(filename)) |
| 216 | { | ||
| 217 | return false; | ||
| 218 | } | ||
| 219 | |||
| 220 | var tilde = filename.IndexOf('~'); | ||
| 221 | return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]); | ||
| 225 | } | 222 | } |
| 226 | 223 | ||
| 227 | /// <summary> | 224 | /// <summary> |
| @@ -273,9 +270,29 @@ namespace WixToolset.Core | |||
| 273 | /// <param name="filename">Filename to make valid.</param> | 270 | /// <param name="filename">Filename to make valid.</param> |
| 274 | /// <param name="replace">Replacement string for invalid characters in filename.</param> | 271 | /// <param name="replace">Replacement string for invalid characters in filename.</param> |
| 275 | /// <returns>Valid filename.</returns> | 272 | /// <returns>Valid filename.</returns> |
| 276 | public static string MakeValidLongFileName(string filename, string replace) | 273 | public static string MakeValidLongFileName(string filename, char replace) |
| 277 | { | 274 | { |
| 278 | return CompilerCore.IllegalLongFilename.Replace(filename, replace); | 275 | if (String.IsNullOrEmpty(filename)) |
| 276 | { | ||
| 277 | return filename; | ||
| 278 | } | ||
| 279 | |||
| 280 | StringBuilder sb = null; | ||
| 281 | |||
| 282 | var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters); | ||
| 283 | while (found != -1) | ||
| 284 | { | ||
| 285 | if (sb == null) | ||
| 286 | { | ||
| 287 | sb = new StringBuilder(filename); | ||
| 288 | } | ||
| 289 | |||
| 290 | sb[found] = replace; | ||
| 291 | |||
| 292 | found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1; | ||
| 293 | } | ||
| 294 | |||
| 295 | return sb?.ToString() ?? filename; | ||
| 279 | } | 296 | } |
| 280 | 297 | ||
| 281 | /// <summary> | 298 | /// <summary> |
| @@ -717,7 +734,7 @@ namespace WixToolset.Core | |||
| 717 | throw new ArgumentNullException("attribute"); | 734 | throw new ArgumentNullException("attribute"); |
| 718 | } | 735 | } |
| 719 | 736 | ||
| 720 | string value = this.GetAttributeValue(sourceLineNumbers, attribute); | 737 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); |
| 721 | 738 | ||
| 722 | if (0 < value.Length) | 739 | if (0 < value.Length) |
| 723 | { | 740 | { |
| @@ -1039,16 +1056,6 @@ namespace WixToolset.Core | |||
| 1039 | return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); | 1056 | return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); |
| 1040 | } | 1057 | } |
| 1041 | 1058 | ||
| 1042 | /// <summary> | ||
| 1043 | /// Finds a compiler extension by namespace URI. | ||
| 1044 | /// </summary> | ||
| 1045 | /// <param name="ns">Namespace the extension supports.</param> | ||
| 1046 | /// <returns>True if found compiler extension or false if nothing matches namespace URI.</returns> | ||
| 1047 | private bool TryFindExtension(XNamespace ns, out ICompilerExtension extension) | ||
| 1048 | { | ||
| 1049 | return this.extensions.TryGetValue(ns, out extension); | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | private static string CreateValueList(ValueListKind kind, IEnumerable<string> values) | 1059 | private static string CreateValueList(ValueListKind kind, IEnumerable<string> values) |
| 1053 | { | 1060 | { |
| 1054 | // Ideally, we could denote the list kind (and the list itself) directly in the | 1061 | // Ideally, we could denote the list kind (and the list itself) directly in the |
diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 7e2485e1..29f240f4 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs | |||
| @@ -4182,30 +4182,6 @@ namespace WixToolset.Core | |||
| 4182 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); | 4182 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); |
| 4183 | break; | 4183 | break; |
| 4184 | } | 4184 | } |
| 4185 | //if (showValue.Length == 0) | ||
| 4186 | //{ | ||
| 4187 | // show = CompilerConstants.IllegalInteger; | ||
| 4188 | //} | ||
| 4189 | //else | ||
| 4190 | //{ | ||
| 4191 | // var showType = Wix.Shortcut.ParseShowType(showValue); | ||
| 4192 | // switch (showType) | ||
| 4193 | // { | ||
| 4194 | // case Wix.Shortcut.ShowType.normal: | ||
| 4195 | // show = 1; | ||
| 4196 | // break; | ||
| 4197 | // case Wix.Shortcut.ShowType.maximized: | ||
| 4198 | // show = 3; | ||
| 4199 | // break; | ||
| 4200 | // case Wix.Shortcut.ShowType.minimized: | ||
| 4201 | // show = 7; | ||
| 4202 | // break; | ||
| 4203 | // default: | ||
| 4204 | // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); | ||
| 4205 | // show = CompilerConstants.IllegalInteger; | ||
| 4206 | // break; | ||
| 4207 | // } | ||
| 4208 | //} | ||
| 4209 | break; | 4185 | break; |
| 4210 | case "Target": | 4186 | case "Target": |
| 4211 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | 4187 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); |
| @@ -4325,11 +4301,11 @@ namespace WixToolset.Core | |||
| 4325 | } | 4301 | } |
| 4326 | else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) | 4302 | else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) |
| 4327 | { | 4303 | { |
| 4328 | target = String.Format(CultureInfo.InvariantCulture, "[{0}]", defaultTarget); | 4304 | target = "[" + defaultTarget + "]"; |
| 4329 | } | 4305 | } |
| 4330 | else if ("File" == parentElementLocalName) | 4306 | else if ("File" == parentElementLocalName) |
| 4331 | { | 4307 | { |
| 4332 | target = String.Format(CultureInfo.InvariantCulture, "[#{0}]", defaultTarget); | 4308 | target = "[#" + defaultTarget + "]"; |
| 4333 | } | 4309 | } |
| 4334 | 4310 | ||
| 4335 | this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) | 4311 | this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) |
diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 578c7dcd..2089f037 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs | |||
| @@ -236,7 +236,7 @@ namespace WixToolset.Core | |||
| 236 | else | 236 | else |
| 237 | { | 237 | { |
| 238 | // Ensure only allowable path characters are in "name" (and change spaces to underscores). | 238 | // Ensure only allowable path characters are in "name" (and change spaces to underscores). |
| 239 | fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), "_"); | 239 | fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_'); |
| 240 | logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); | 240 | logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); |
| 241 | } | 241 | } |
| 242 | 242 | ||
diff --git a/src/WixToolset.Core/Compiler_UI.cs b/src/WixToolset.Core/Compiler_UI.cs index 36d2e4e9..9353d966 100644 --- a/src/WixToolset.Core/Compiler_UI.cs +++ b/src/WixToolset.Core/Compiler_UI.cs | |||
| @@ -1740,7 +1740,7 @@ namespace WixToolset.Core | |||
| 1740 | { | 1740 | { |
| 1741 | // if we're not looking at a standard action or a formatted string then create a reference | 1741 | // if we're not looking at a standard action or a formatted string then create a reference |
| 1742 | // to the custom action. | 1742 | // to the custom action. |
| 1743 | if (!WindowsInstallerStandard.IsStandardAction(argument) && !Common.ContainsProperty(argument)) | 1743 | if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument)) |
| 1744 | { | 1744 | { |
| 1745 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument); | 1745 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument); |
| 1746 | } | 1746 | } |
diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index db465354..81a18e24 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs | |||
| @@ -9,7 +9,6 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 9 | using System.IO; | 9 | using System.IO; |
| 10 | using System.Security.Cryptography; | 10 | using System.Security.Cryptography; |
| 11 | using System.Text; | 11 | using System.Text; |
| 12 | using System.Text.RegularExpressions; | ||
| 13 | using System.Xml.Linq; | 12 | using System.Xml.Linq; |
| 14 | using WixToolset.Data; | 13 | using WixToolset.Data; |
| 15 | using WixToolset.Data.Symbols; | 14 | using WixToolset.Data.Symbols; |
| @@ -20,19 +19,6 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 20 | 19 | ||
| 21 | internal class ParseHelper : IParseHelper | 20 | internal class ParseHelper : IParseHelper |
| 22 | { | 21 | { |
| 23 | private const string LegalLongFilenameCharacters = @"[^\\\?|><:/\*""]"; // opposite of illegal above. | ||
| 24 | private static readonly Regex LegalLongFilename = new Regex(String.Concat("^", LegalLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled); | ||
| 25 | |||
| 26 | private const string LegalRelativeLongFilenameCharacters = @"[^\?|><:/\*""]"; // (like legal long, but we allow '\') illegal: ? | > < : / * " | ||
| 27 | private static readonly Regex LegalRelativeLongFilename = new Regex(String.Concat("^", LegalRelativeLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled); | ||
| 28 | |||
| 29 | private const string LegalWildcardLongFilenameCharacters = @"[^\\|><:/""]"; // illegal: \ | > < : / " | ||
| 30 | private static readonly Regex LegalWildcardLongFilename = new Regex(String.Concat("^", LegalWildcardLongFilenameCharacters, @"{1,259}$")); | ||
| 31 | |||
| 32 | private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?<access>public|internal|protected|private)\s+)?(?<id>[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); | ||
| 33 | |||
| 34 | private static readonly Regex PutGuidHere = new Regex(@"PUT\-GUID\-(?:\d+\-)?HERE", RegexOptions.Singleline); | ||
| 35 | |||
| 36 | public ParseHelper(IWixToolsetServiceProvider serviceProvider) | 22 | public ParseHelper(IWixToolsetServiceProvider serviceProvider) |
| 37 | { | 23 | { |
| 38 | this.ServiceProvider = serviceProvider; | 24 | this.ServiceProvider = serviceProvider; |
| @@ -119,7 +105,7 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 119 | id = parentId; | 105 | id = parentId; |
| 120 | 106 | ||
| 121 | var pathStartsAt = 0; | 107 | var pathStartsAt = 0; |
| 122 | if (inlineSyntax[0].EndsWith(":")) | 108 | if (inlineSyntax[0].EndsWith(":", StringComparison.Ordinal)) |
| 123 | { | 109 | { |
| 124 | // TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide? | 110 | // TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide? |
| 125 | //if (null != parentId) | 111 | //if (null != parentId) |
| @@ -415,44 +401,34 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 415 | var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; | 401 | var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; |
| 416 | var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); | 402 | var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); |
| 417 | 403 | ||
| 418 | if (String.IsNullOrEmpty(value) && canBeEmpty) | 404 | if (String.IsNullOrEmpty(value)) |
| 419 | { | ||
| 420 | return String.Empty; | ||
| 421 | } | ||
| 422 | else if (!String.IsNullOrEmpty(value)) | ||
| 423 | { | 405 | { |
| 424 | // If the value starts and ends with braces or parenthesis, accept that and strip them off. | 406 | if (canBeEmpty) |
| 425 | if ((value.StartsWith("{", StringComparison.Ordinal) && value.EndsWith("}", StringComparison.Ordinal)) | ||
| 426 | || (value.StartsWith("(", StringComparison.Ordinal) && value.EndsWith(")", StringComparison.Ordinal))) | ||
| 427 | { | 407 | { |
| 428 | value = value.Substring(1, value.Length - 2); | 408 | return String.Empty; |
| 429 | } | 409 | } |
| 430 | 410 | } | |
| 431 | if (generatable && "*".Equals(value, StringComparison.Ordinal)) | 411 | else |
| 412 | { | ||
| 413 | if (generatable && value == "*") | ||
| 432 | { | 414 | { |
| 433 | return value; | 415 | return value; |
| 434 | } | 416 | } |
| 435 | 417 | ||
| 436 | if (ParseHelper.PutGuidHere.IsMatch(value)) | 418 | if (Guid.TryParse(value, out var guid)) |
| 437 | { | 419 | { |
| 438 | this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | 420 | return guid.ToString("B").ToUpperInvariant(); |
| 439 | return CompilerConstants.IllegalGuid; | ||
| 440 | } | 421 | } |
| 441 | else if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) | 422 | |
| 423 | if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) | ||
| 442 | { | 424 | { |
| 443 | return value; | 425 | return value; |
| 444 | } | 426 | } |
| 445 | else if (Guid.TryParse(value, out var guid)) | ||
| 446 | { | ||
| 447 | var uppercaseGuid = guid.ToString().ToUpperInvariant(); | ||
| 448 | 427 | ||
| 449 | // TODO: This used to be a pedantic error, what should it be now? | 428 | if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) || |
| 450 | //if (uppercaseGuid != value) | 429 | value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase)) |
| 451 | //{ | 430 | { |
| 452 | // this.Messaging.Write(WixErrors.GuidContainsLowercaseLetters(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | 431 | this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); |
| 453 | //} | ||
| 454 | |||
| 455 | return String.Concat("{", uppercaseGuid, "}"); | ||
| 456 | } | 432 | } |
| 457 | else | 433 | else |
| 458 | { | 434 | { |
| @@ -468,19 +444,45 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 468 | var access = AccessModifier.Public; | 444 | var access = AccessModifier.Public; |
| 469 | var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); | 445 | var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); |
| 470 | 446 | ||
| 471 | var match = ParseHelper.LegalIdentifierWithAccess.Match(value); | 447 | var separator = value.IndexOf(' '); |
| 472 | if (!match.Success) | 448 | if (separator > 0) |
| 473 | { | 449 | { |
| 474 | return null; | 450 | var prefix = value.Substring(0, separator); |
| 451 | switch (prefix) | ||
| 452 | { | ||
| 453 | case "public": | ||
| 454 | case "package": | ||
| 455 | access = AccessModifier.Public; | ||
| 456 | break; | ||
| 457 | |||
| 458 | case "internal": | ||
| 459 | case "library": | ||
| 460 | access = AccessModifier.Internal; | ||
| 461 | break; | ||
| 462 | |||
| 463 | case "protected": | ||
| 464 | case "file": | ||
| 465 | access = AccessModifier.Protected; | ||
| 466 | break; | ||
| 467 | |||
| 468 | case "private": | ||
| 469 | case "fragment": | ||
| 470 | access = AccessModifier.Private; | ||
| 471 | break; | ||
| 472 | |||
| 473 | default: | ||
| 474 | return null; | ||
| 475 | } | ||
| 476 | |||
| 477 | value = value.Substring(separator + 1).Trim(); | ||
| 475 | } | 478 | } |
| 476 | else if (match.Groups["access"].Success) | 479 | |
| 480 | if (!Common.IsIdentifier(value)) | ||
| 477 | { | 481 | { |
| 478 | access = (AccessModifier)Enum.Parse(typeof(AccessModifier), match.Groups["access"].Value, true); | 482 | this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); |
| 483 | return null; | ||
| 479 | } | 484 | } |
| 480 | 485 | else if (72 < value.Length) | |
| 481 | value = match.Groups["id"].Value; | ||
| 482 | |||
| 483 | if (Common.IsIdentifier(value) && 72 < value.Length) | ||
| 484 | { | 486 | { |
| 485 | this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | 487 | this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); |
| 486 | } | 488 | } |
| @@ -520,7 +522,7 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 520 | } | 522 | } |
| 521 | else if (resultUsedToCreateReference && 1 == result.Length) | 523 | else if (resultUsedToCreateReference && 1 == result.Length) |
| 522 | { | 524 | { |
| 523 | if (value.EndsWith("\\")) | 525 | if (value.EndsWith("\\", StringComparison.Ordinal)) |
| 524 | { | 526 | { |
| 525 | if (!this.IsValidLongFilename(result[0], false, false)) | 527 | if (!this.IsValidLongFilename(result[0], false, false)) |
| 526 | { | 528 | { |
| @@ -547,7 +549,7 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 547 | } | 549 | } |
| 548 | } | 550 | } |
| 549 | 551 | ||
| 550 | if (1 < result.Length && !value.EndsWith("\\")) | 552 | if (1 < result.Length && !value.EndsWith("\\", StringComparison.Ordinal)) |
| 551 | { | 553 | { |
| 552 | this.Messaging.Write(WarningMessages.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | 554 | this.Messaging.Write(WarningMessages.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); |
| 553 | } | 555 | } |
| @@ -776,14 +778,7 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 776 | 778 | ||
| 777 | public bool IsValidLocIdentifier(string identifier) | 779 | public bool IsValidLocIdentifier(string identifier) |
| 778 | { | 780 | { |
| 779 | if (String.IsNullOrEmpty(identifier)) | 781 | return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc"; |
| 780 | { | ||
| 781 | return false; | ||
| 782 | } | ||
| 783 | |||
| 784 | var match = Common.WixVariableRegex.Match(identifier); | ||
| 785 | |||
| 786 | return (match.Success && "loc" == match.Groups["namespace"].Value && 0 == match.Index && identifier.Length == match.Length); | ||
| 787 | } | 782 | } |
| 788 | 783 | ||
| 789 | public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) | 784 | public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) |
| @@ -792,29 +787,38 @@ namespace WixToolset.Core.ExtensibilityServices | |||
| 792 | { | 787 | { |
| 793 | return false; | 788 | return false; |
| 794 | } | 789 | } |
| 790 | else if (filename.Length > 259) | ||
| 791 | { | ||
| 792 | return false; | ||
| 793 | } | ||
| 795 | 794 | ||
| 796 | // Check for a non-period character (all periods is not legal) | 795 | // Check for a non-period character (all periods is not legal) |
| 797 | var nonPeriodFound = false; | 796 | var allPeriods = true; |
| 798 | foreach (var character in filename) | 797 | foreach (var character in filename) |
| 799 | { | 798 | { |
| 800 | if ('.' != character) | 799 | if ('.' != character) |
| 801 | { | 800 | { |
| 802 | nonPeriodFound = true; | 801 | allPeriods = false; |
| 803 | break; | 802 | break; |
| 804 | } | 803 | } |
| 805 | } | 804 | } |
| 806 | 805 | ||
| 806 | if (allPeriods) | ||
| 807 | { | ||
| 808 | return false; | ||
| 809 | } | ||
| 810 | |||
| 807 | if (allowWildcards) | 811 | if (allowWildcards) |
| 808 | { | 812 | { |
| 809 | return (nonPeriodFound && ParseHelper.LegalWildcardLongFilename.IsMatch(filename)); | 813 | return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1; |
| 810 | } | 814 | } |
| 811 | else if (allowRelative) | 815 | else if (allowRelative) |
| 812 | { | 816 | { |
| 813 | return (nonPeriodFound && ParseHelper.LegalRelativeLongFilename.IsMatch(filename)); | 817 | return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1; |
| 814 | } | 818 | } |
| 815 | else | 819 | else |
| 816 | { | 820 | { |
| 817 | return (nonPeriodFound && ParseHelper.LegalLongFilename.IsMatch(filename)); | 821 | return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1; |
| 818 | } | 822 | } |
| 819 | } | 823 | } |
| 820 | 824 | ||
diff --git a/src/WixToolset.Core/ParsedWixVariable.cs b/src/WixToolset.Core/ParsedWixVariable.cs new file mode 100644 index 00000000..9d308b77 --- /dev/null +++ b/src/WixToolset.Core/ParsedWixVariable.cs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | internal class ParsedWixVariable | ||
| 6 | { | ||
| 7 | public int Index { get; set; } | ||
| 8 | |||
| 9 | public int Length { get; set; } | ||
| 10 | |||
| 11 | public string Namespace { get; set; } | ||
| 12 | |||
| 13 | public string Name { get; set; } | ||
| 14 | |||
| 15 | public string Scope { get; set; } | ||
| 16 | |||
| 17 | public string DefaultValue { get; set; } | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/WixToolset.Core/VariableResolver.cs b/src/WixToolset.Core/VariableResolver.cs index 88067673..140e7def 100644 --- a/src/WixToolset.Core/VariableResolver.cs +++ b/src/WixToolset.Core/VariableResolver.cs | |||
| @@ -79,150 +79,108 @@ namespace WixToolset.Core | |||
| 79 | 79 | ||
| 80 | public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown) | 80 | public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown) |
| 81 | { | 81 | { |
| 82 | var matches = Common.WixVariableRegex.Matches(value); | 82 | var start = 0; |
| 83 | var defaulted = true; | ||
| 84 | var delayed = false; | ||
| 85 | var updated = false; | ||
| 83 | 86 | ||
| 84 | // the value is the default unless it's substituted further down | 87 | while (Common.TryParseWixVariable(value, start, out var parsed)) |
| 85 | var result = this.ServiceProvider.GetService<IVariableResolution>(); | ||
| 86 | result.IsDefault = true; | ||
| 87 | result.Value = value; | ||
| 88 | |||
| 89 | var finalizeEscapes = false; | ||
| 90 | |||
| 91 | while (matches.Count > 0) | ||
| 92 | { | 88 | { |
| 93 | var updatedResultThisPass = false; | 89 | var variableNamespace = parsed.Namespace; |
| 94 | var sb = new StringBuilder(value); | 90 | var variableId = parsed.Name; |
| 91 | var variableDefaultValue = parsed.DefaultValue; | ||
| 95 | 92 | ||
| 96 | // notice how this code walks backward through the list | 93 | // check for an escape sequence of !! indicating the match is not a variable expression |
| 97 | // because it modifies the string as we move through it | 94 | if (0 < parsed.Index && '!' == value[parsed.Index - 1]) |
| 98 | for (var i = matches.Count - 1; 0 <= i; i--) | ||
| 99 | { | 95 | { |
| 100 | var variableNamespace = matches[i].Groups["namespace"].Value; | 96 | var sb = new StringBuilder(value); |
| 101 | var variableId = matches[i].Groups["fullname"].Value; | 97 | sb.Remove(parsed.Index - 1, 1); |
| 102 | string variableDefaultValue = null; | 98 | value = sb.ToString(); |
| 103 | 99 | ||
| 104 | // get the default value if one was specified | 100 | updated = true; |
| 105 | if (matches[i].Groups["value"].Success) | 101 | start = parsed.Index + parsed.Length - 1; |
| 106 | { | ||
| 107 | variableDefaultValue = matches[i].Groups["value"].Value; | ||
| 108 | 102 | ||
| 109 | // localization variables do not support inline default values | 103 | continue; |
| 110 | if ("loc" == variableNamespace) | 104 | } |
| 111 | { | 105 | |
| 112 | this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); | 106 | string resolvedValue = null; |
| 113 | } | 107 | |
| 108 | if ("loc" == variableNamespace) | ||
| 109 | { | ||
| 110 | // localization variables do not support inline default values | ||
| 111 | if (variableDefaultValue != null) | ||
| 112 | { | ||
| 113 | this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); | ||
| 114 | continue; | ||
| 114 | } | 115 | } |
| 115 | 116 | ||
| 116 | // get the scope if one was specified | 117 | if (this.locVariables.TryGetValue(variableId, out var bindVariable)) |
| 117 | if (matches[i].Groups["scope"].Success) | ||
| 118 | { | 118 | { |
| 119 | if ("bind" == variableNamespace) | 119 | resolvedValue = bindVariable.Value; |
| 120 | { | 120 | } |
| 121 | variableId = matches[i].Groups["name"].Value; | 121 | } |
| 122 | } | 122 | else if ("wix" == variableNamespace) |
| 123 | { | ||
| 124 | if (this.wixVariables.TryGetValue(variableId, out var bindVariable)) | ||
| 125 | { | ||
| 126 | resolvedValue = bindVariable.Value ?? String.Empty; | ||
| 127 | defaulted = false; | ||
| 123 | } | 128 | } |
| 129 | else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified | ||
| 130 | { | ||
| 131 | resolvedValue = variableDefaultValue; | ||
| 132 | } | ||
| 133 | } | ||
| 124 | 134 | ||
| 125 | // check for an escape sequence of !! indicating the match is not a variable expression | 135 | if ("bind" == variableNamespace) |
| 126 | if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1]) | 136 | { |
| 137 | // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort. | ||
| 138 | delayed = true; | ||
| 139 | start = parsed.Index + parsed.Length - 1; | ||
| 140 | } | ||
| 141 | else | ||
| 142 | { | ||
| 143 | // insert the resolved value if it was found or display an error | ||
| 144 | if (null != resolvedValue) | ||
| 127 | { | 145 | { |
| 128 | if (finalizeEscapes) | 146 | if (parsed.Index == 0 && parsed.Length == value.Length) |
| 129 | { | 147 | { |
| 130 | sb.Remove(matches[i].Index - 1, 1); | 148 | value = resolvedValue; |
| 131 | |||
| 132 | result.UpdatedValue = true; | ||
| 133 | } | 149 | } |
| 134 | else | 150 | else |
| 135 | { | 151 | { |
| 136 | continue; | 152 | var sb = new StringBuilder(value); |
| 153 | sb.Remove(parsed.Index, parsed.Length); | ||
| 154 | sb.Insert(parsed.Index, resolvedValue); | ||
| 155 | value = sb.ToString(); | ||
| 137 | } | 156 | } |
| 157 | |||
| 158 | updated = true; | ||
| 159 | start = parsed.Index; | ||
| 138 | } | 160 | } |
| 139 | else | 161 | else |
| 140 | { | 162 | { |
| 141 | string resolvedValue = null; | 163 | if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable |
| 142 | |||
| 143 | if ("loc" == variableNamespace) | ||
| 144 | { | 164 | { |
| 145 | // warn about deprecated syntax of $(loc.var) | 165 | this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId)); |
| 146 | if ('$' == sb[matches[i].Index]) | ||
| 147 | { | ||
| 148 | this.Messaging.Write(WarningMessages.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId)); | ||
| 149 | } | ||
| 150 | |||
| 151 | if (this.locVariables.TryGetValue(variableId, out var bindVariable)) | ||
| 152 | { | ||
| 153 | resolvedValue = bindVariable.Value; | ||
| 154 | } | ||
| 155 | } | 166 | } |
| 156 | else if ("wix" == variableNamespace) | 167 | else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable |
| 157 | { | 168 | { |
| 158 | // illegal syntax of $(wix.var) | 169 | this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); |
| 159 | if ('$' == sb[matches[i].Index]) | ||
| 160 | { | ||
| 161 | this.Messaging.Write(ErrorMessages.IllegalWixVariablePrefix(sourceLineNumbers, variableId)); | ||
| 162 | } | ||
| 163 | else | ||
| 164 | { | ||
| 165 | if (this.wixVariables.TryGetValue(variableId, out var bindVariable)) | ||
| 166 | { | ||
| 167 | resolvedValue = bindVariable.Value ?? String.Empty; | ||
| 168 | result.IsDefault = false; | ||
| 169 | } | ||
| 170 | else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified | ||
| 171 | { | ||
| 172 | resolvedValue = variableDefaultValue; | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | 170 | } |
| 176 | 171 | ||
| 177 | if ("bind" == variableNamespace) | 172 | start = parsed.Index + parsed.Length; |
| 178 | { | ||
| 179 | // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort | ||
| 180 | result.DelayedResolve = true; | ||
| 181 | } | ||
| 182 | else | ||
| 183 | { | ||
| 184 | // insert the resolved value if it was found or display an error | ||
| 185 | if (null != resolvedValue) | ||
| 186 | { | ||
| 187 | sb.Remove(matches[i].Index, matches[i].Length); | ||
| 188 | sb.Insert(matches[i].Index, resolvedValue); | ||
| 189 | |||
| 190 | result.UpdatedValue = true; | ||
| 191 | updatedResultThisPass = true; | ||
| 192 | } | ||
| 193 | else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable | ||
| 194 | { | ||
| 195 | this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId)); | ||
| 196 | } | ||
| 197 | else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable | ||
| 198 | { | ||
| 199 | this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | 173 | } |
| 203 | } | 174 | } |
| 204 | |||
| 205 | result.Value = sb.ToString(); | ||
| 206 | value = result.Value; | ||
| 207 | |||
| 208 | if (finalizeEscapes) | ||
| 209 | { | ||
| 210 | // escaped references have been un-escaped, so we're done | ||
| 211 | break; | ||
| 212 | } | ||
| 213 | else if (updatedResultThisPass) | ||
| 214 | { | ||
| 215 | // we substituted loc strings, so make another pass to see if that brought in more loc strings | ||
| 216 | matches = Common.WixVariableRegex.Matches(value); | ||
| 217 | } | ||
| 218 | else | ||
| 219 | { | ||
| 220 | // make one final pass to un-escape any escaped references | ||
| 221 | finalizeEscapes = true; | ||
| 222 | } | ||
| 223 | } | 175 | } |
| 224 | 176 | ||
| 225 | return result; | 177 | return new VariableResolution |
| 178 | { | ||
| 179 | DelayedResolve = delayed, | ||
| 180 | IsDefault = defaulted, | ||
| 181 | UpdatedValue = updated, | ||
| 182 | Value = value, | ||
| 183 | }; | ||
| 226 | } | 184 | } |
| 227 | 185 | ||
| 228 | private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable) | 186 | private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable) |
