diff options
Diffstat (limited to 'src/WixToolset.Core/Common.cs')
-rw-r--r-- | src/WixToolset.Core/Common.cs | 271 |
1 files changed, 204 insertions, 67 deletions
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 | } |