aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Common.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Common.cs')
-rw-r--r--src/WixToolset.Core/Common.cs271
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}