// 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.
namespace WixToolset
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using WixToolset.Data;
using WixToolset.Data.Tuples;
using WixToolset.Extensibility;
using Wix = WixToolset.Data.Serialize;
internal enum ValueListKind
{
///
/// A list of values with nothing before the final value.
///
None,
///
/// A list of values with 'and' before the final value.
///
And,
///
/// A list of values with 'or' before the final value.
///
Or
}
///
/// Core class for the compiler.
///
internal sealed class CompilerCore //: ICompilerCore
{
internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/";
internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled);
private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * "
private static readonly Regex IllegalLongFilename = new Regex(IllegalLongFilenameCharacters, RegexOptions.Compiled);
private const string LegalLongFilenameCharacters = @"[^\\\?|><:/\*""]"; // opposite of illegal above.
private static readonly Regex LegalLongFilename = new Regex(String.Concat("^", LegalLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
private const string LegalRelativeLongFilenameCharacters = @"[^\?|><:/\*""]"; // (like legal long, but we allow '\') illegal: ? | > < : / * "
private static readonly Regex LegalRelativeLongFilename = new Regex(String.Concat("^", LegalRelativeLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
private const string LegalWildcardLongFilenameCharacters = @"[^\\|><:/""]"; // illegal: \ | > < : / "
private static readonly Regex LegalWildcardLongFilename = new Regex(String.Concat("^", LegalWildcardLongFilenameCharacters, @"{1,259}$"));
private static readonly Regex PutGuidHere = new Regex(@"PUT\-GUID\-(?:\d+\-)?HERE", RegexOptions.Singleline);
private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?public|internal|protected|private)\s+)?(?[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
public const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
// Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113)
private static readonly List BuiltinBundleVariables = new List(
new string[] {
"AdminToolsFolder",
"AppDataFolder",
"CommonAppDataFolder",
"CommonFiles64Folder",
"CommonFilesFolder",
"CompatibilityMode",
"Date",
"DesktopFolder",
"FavoritesFolder",
"FontsFolder",
"InstallerName",
"InstallerVersion",
"LocalAppDataFolder",
"LogonUser",
"MyPicturesFolder",
"NTProductType",
"NTSuiteBackOffice",
"NTSuiteDataCenter",
"NTSuiteEnterprise",
"NTSuitePersonal",
"NTSuiteSmallBusiness",
"NTSuiteSmallBusinessRestricted",
"NTSuiteWebServer",
"PersonalFolder",
"Privileged",
"ProgramFiles64Folder",
"ProgramFiles6432Folder",
"ProgramFilesFolder",
"ProgramMenuFolder",
"RebootPending",
"SendToFolder",
"ServicePackLevel",
"StartMenuFolder",
"StartupFolder",
"System64Folder",
"SystemFolder",
"TempFolder",
"TemplateFolder",
"TerminalServer",
"UserLanguageID",
"UserUILanguageID",
"VersionMsi",
"VersionNT",
"VersionNT64",
"WindowsFolder",
"WindowsVolume",
"WixBundleAction",
"WixBundleForcedRestartPackage",
"WixBundleElevated",
"WixBundleInstalled",
"WixBundleProviderKey",
"WixBundleTag",
"WixBundleVersion",
});
private static readonly List DisallowedMsiProperties = new List(
new string[] {
"ACTION",
"ADDLOCAL",
"ADDSOURCE",
"ADDDEFAULT",
"ADVERTISE",
"ALLUSERS",
"REBOOT",
"REINSTALL",
"REINSTALLMODE",
"REMOVE"
});
private Dictionary extensions;
private ITupleDefinitionCreator creator;
private Intermediate intermediate;
private HashSet activeSectionInlinedDirectoryIds;
private HashSet activeSectionSimpleReferences;
///
/// Constructor for all compiler core.
///
/// The Intermediate object representing compiled source document.
/// The WiX extensions collection.
internal CompilerCore(Intermediate intermediate, ITupleDefinitionCreator creator, Dictionary extensions)
{
this.extensions = extensions;
this.creator = creator;
this.intermediate = intermediate;
}
///
/// Gets the section the compiler is currently emitting symbols into.
///
/// The section the compiler is currently emitting symbols into.
public IntermediateSection ActiveSection { get; private set; }
///
/// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
///
/// The platform which the compiler will use when defaulting 64-bit attributes and elements.
public Platform CurrentPlatform { get; set; }
///
/// Gets whether the compiler core encountered an error while processing.
///
/// Flag if core encountered an error during processing.
public bool EncounteredError => Messaging.Instance.EncounteredError;
///
/// Gets or sets the option to show pedantic messages.
///
/// The option to show pedantic messages.
public bool ShowPedanticMessages { get; set; }
///
/// Convert a bit array into an int value.
///
/// The bit array to convert.
/// The converted int value.
public int CreateIntegerFromBitArray(BitArray bits)
{
if (32 != bits.Length)
{
throw new ArgumentException(String.Format("Can only convert a bit array with 32-bits to integer. Actual number of bits in array: {0}", bits.Length), "bits");
}
int[] intArray = new int[1];
bits.CopyTo(intArray, 0);
return intArray[0];
}
///
/// Sets a bit in a bit array based on the index at which an attribute name was found in a string array.
///
/// Array of attributes that map to bits.
/// Name of attribute to check.
/// Value of attribute to check.
/// The bit array in which the bit will be set if found.
/// The offset into the bit array.
/// true if the bit was set; false otherwise.
public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset)
{
for (int i = 0; i < attributeNames.Length; i++)
{
if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal))
{
bits.Set(i + offset, YesNoType.Yes == attributeValue);
return true;
}
}
return false;
}
///
/// Verifies that a filename is ambiguous.
///
/// Filename to verify.
/// true if the filename is ambiguous; false otherwise.
public static bool IsAmbiguousFilename(string filename)
{
if (null == filename || 0 == filename.Length)
{
return false;
}
return CompilerCore.AmbiguousFilename.IsMatch(filename);
}
///
/// Verifies that a value is a legal identifier.
///
/// The value to verify.
/// true if the value is an identifier; false otherwise.
public bool IsValidIdentifier(string value)
{
return Common.IsIdentifier(value);
}
///
/// Verifies if an identifier is a valid loc identifier.
///
/// Identifier to verify.
/// True if the identifier is a valid loc identifier.
public bool IsValidLocIdentifier(string identifier)
{
if (String.IsNullOrEmpty(identifier))
{
return false;
}
Match match = Common.WixVariableRegex.Match(identifier);
return (match.Success && "loc" == match.Groups["namespace"].Value && 0 == match.Index && identifier.Length == match.Length);
}
///
/// Verifies if a filename is a valid long filename.
///
/// Filename to verify.
/// true if wildcards are allowed in the filename.
/// true if relative paths are allowed in the filename.
/// True if the filename is a valid long filename
public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false)
{
if (String.IsNullOrEmpty(filename))
{
return false;
}
// check for a non-period character (all periods is not legal)
bool nonPeriodFound = false;
foreach (char character in filename)
{
if ('.' != character)
{
nonPeriodFound = true;
break;
}
}
if (allowWildcards)
{
return (nonPeriodFound && CompilerCore.LegalWildcardLongFilename.IsMatch(filename));
}
else if (allowRelative)
{
return (nonPeriodFound && CompilerCore.LegalRelativeLongFilename.IsMatch(filename));
}
else
{
return (nonPeriodFound && CompilerCore.LegalLongFilename.IsMatch(filename));
}
}
///
/// Verifies if a filename is a valid short filename.
///
/// Filename to verify.
/// true if wildcards are allowed in the filename.
/// True if the filename is a valid short filename
public bool IsValidShortFilename(string filename, bool allowWildcards)
{
return Common.IsValidShortFilename(filename, allowWildcards);
}
///
/// Replaces the illegal filename characters to create a legal name.
///
/// Filename to make valid.
/// Replacement string for invalid characters in filename.
/// Valid filename.
public static string MakeValidLongFileName(string filename, string replace)
{
return CompilerCore.IllegalLongFilename.Replace(filename, replace);
}
///
/// Creates a short file/directory name using an identifier and long file/directory name as input.
///
/// The long file/directory name.
/// The option to keep the extension on generated short names.
/// true if wildcards are allowed in the filename.
/// Any additional information to include in the hash for the generated short name.
/// The generated 8.3-compliant short file/directory name.
public string CreateShortName(string longName, bool keepExtension, bool allowWildcards, params string[] args)
{
// canonicalize the long name if its not a localization identifier (they are case-sensitive)
if (!this.IsValidLocIdentifier(longName))
{
longName = longName.ToLowerInvariant();
}
// collect all the data
List strings = new List(1 + args.Length);
strings.Add(longName);
strings.AddRange(args);
// prepare for hashing
string stringData = String.Join("|", strings);
byte[] data = Encoding.UTF8.GetBytes(stringData);
// hash the data
byte[] hash;
using (SHA1 sha1 = new SHA1CryptoServiceProvider())
{
hash = sha1.ComputeHash(data);
}
// generate the short file/directory name without an extension
StringBuilder shortName = new StringBuilder(Convert.ToBase64String(hash));
shortName.Remove(8, shortName.Length - 8).Replace('+', '-').Replace('/', '_');
if (keepExtension)
{
string extension = Path.GetExtension(longName);
if (4 < extension.Length)
{
extension = extension.Substring(0, 4);
}
shortName.Append(extension);
// check the generated short name to ensure its still legal (the extension may not be legal)
if (!this.IsValidShortFilename(shortName.ToString(), allowWildcards))
{
// remove the extension (by truncating the generated file name back to the generated characters)
shortName.Length -= extension.Length;
}
}
return shortName.ToString().ToLowerInvariant();
}
///
/// Verifies the given string is a valid product version.
///
/// The product version to verify.
/// True if version is a valid product version
public static bool IsValidProductVersion(string version)
{
if (!Common.IsValidBinderVariable(version))
{
Version ver = new Version(version);
if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build)
{
return false;
}
}
return true;
}
///
/// Verifies the given string is a valid module or bundle version.
///
/// The version to verify.
/// True if version is a valid module or bundle version.
public static bool IsValidModuleOrBundleVersion(string version)
{
return Common.IsValidModuleOrBundleVersion(version);
}
///
/// Get an element's inner text and trims any extra whitespace.
///
/// The element with inner text to be trimmed.
/// The node's inner text trimmed.
public string GetTrimmedInnerText(XElement element)
{
string value = Common.GetInnerText(element);
return (null == value) ? null : value.Trim();
}
///
/// Gets element's inner text and ensure's it is safe for use in a condition by trimming any extra whitespace.
///
/// The element to ensure inner text is a condition.
/// The value converted into a safe condition.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public string GetConditionInnerText(XElement element)
{
string value = element.Value;
if (0 < value.Length)
{
value = value.Trim();
value = value.Replace('\t', ' ');
value = value.Replace('\r', ' ');
value = value.Replace('\n', ' ');
}
else // return null for a non-existant condition
{
value = null;
}
return value;
}
///
/// Creates a version 3 name-based UUID.
///
/// The namespace UUID.
/// The value.
/// The generated GUID for the given namespace and value.
public string CreateGuid(Guid namespaceGuid, string value)
{
return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant();
}
///
/// Creates a row in the active section.
///
/// Source and line number of current row.
/// Name of table to create row in.
/// New row.
public IntermediateTuple CreateRow(SourceLineNumber sourceLineNumbers, TupleDefinitionType tupleType, Identifier identifier = null)
{
return this.CreateRow(sourceLineNumbers, tupleType, this.ActiveSection, identifier);
}
///
/// Creates a row in the active given .
///
/// Source and line number of current row.
/// Name of table to create row in.
/// The section to which the row is added. If null, the row is added to the active section.
/// New row.
internal IntermediateTuple CreateRow(SourceLineNumber sourceLineNumbers, TupleDefinitionType tupleType, IntermediateSection section, Identifier identifier = null)
{
var tupleDefinition = TupleDefinitions.ByType(tupleType);
var row = tupleDefinition.CreateTuple(sourceLineNumbers, identifier);
if (null != identifier)
{
if (row.Definition.FieldDefinitions[0].Type == IntermediateFieldType.Number)
{
row.Set(0, Convert.ToInt32(identifier.Id));
}
else
{
row.Set(0, identifier.Id);
}
}
section.Tuples.Add(row);
return row;
}
///
/// Creates directories using the inline directory syntax.
///
/// Source line information.
/// The attribute to parse.
/// Optional identifier of parent directory.
/// Identifier of the leaf directory created.
public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId)
{
string id = null;
string[] inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true);
if (null != inlineSyntax)
{
// Special case the single entry in the inline syntax since it is the most common case
// and needs no extra processing. It's just a reference to an existing directory.
if (1 == inlineSyntax.Length)
{
id = inlineSyntax[0];
this.CreateSimpleReference(sourceLineNumbers, "Directory", id);
}
else // start creating rows for the entries in the inline syntax
{
id = parentId;
int pathStartsAt = 0;
if (inlineSyntax[0].EndsWith(":"))
{
// TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide?
//if (null != parentId)
//{
// this.core.OnMessage(WixErrors.Xxx(sourceLineNumbers));
//}
id = inlineSyntax[0].TrimEnd(':');
this.CreateSimpleReference(sourceLineNumbers, "Directory", id);
pathStartsAt = 1;
}
for (int i = pathStartsAt; i < inlineSyntax.Length; ++i)
{
Identifier inlineId = this.CreateDirectoryRow(sourceLineNumbers, null, id, inlineSyntax[i]);
id = inlineId.Id;
}
}
}
return id;
}
///
/// Creates a patch resource reference to the list of resoures to be filtered when producing a patch. This method should only be used when processing children of a patch family.
///
/// Source and line number of current row.
/// Name of table to create row in.
/// Array of keys that make up the primary key of the table.
/// New row.
public void CreatePatchFamilyChildReference(SourceLineNumber sourceLineNumbers, string tableName, params string[] primaryKeys)
{
var patchReferenceRow = this.CreateRow(sourceLineNumbers, TupleDefinitionType.WixPatchRef);
patchReferenceRow.Set(0, tableName);
patchReferenceRow.Set(1, String.Join("/", primaryKeys));
}
///
/// Creates a Registry row in the active section.
///
/// Source and line number of the current row.
/// The registry entry root.
/// The registry entry key.
/// The registry entry name.
/// The registry entry value.
/// The component which will control installation/uninstallation of the registry entry.
/// If true, "escape" leading '#' characters so the value is written as a REG_SZ.
public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, int root, string key, string name, string value, string componentId, bool escapeLeadingHash)
{
Identifier id = null;
if (!this.EncounteredError)
{
if (-1 > root || 3 < root)
{
throw new ArgumentOutOfRangeException("root");
}
if (null == key)
{
throw new ArgumentNullException("key");
}
if (null == componentId)
{
throw new ArgumentNullException("componentId");
}
// escape the leading '#' character for string registry values
if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal))
{
value = String.Concat("#", value);
}
id = this.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name));
var row = this.CreateRow(sourceLineNumbers, TupleDefinitionType.Registry, id);
row.Set(1, root);
row.Set(2, key);
row.Set(3, name);
row.Set(4, value);
row.Set(5, componentId);
}
return id;
}
///
/// Creates a Registry row in the active section.
///
/// Source and line number of the current row.
/// The registry entry root.
/// The registry entry key.
/// The registry entry name.
/// The registry entry value.
/// The component which will control installation/uninstallation of the registry entry.
public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, int root, string key, string name, string value, string componentId)
{
return this.CreateRegistryRow(sourceLineNumbers, root, key, name, value, componentId, true);
}
///
/// Create a WixSimpleReference row in the active section.
///
/// Source line information for the row.
/// The table name of the simple reference.
/// The primary keys of the simple reference.
public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string tableName, params string[] primaryKeys)
{
if (!this.EncounteredError)
{
string joinedKeys = String.Join("/", primaryKeys);
string id = String.Concat(tableName, ":", joinedKeys);
// If this simple reference hasn't been added to the active section already, add it.
if (this.activeSectionSimpleReferences.Add(id))
{
var wixSimpleReferenceRow = (WixSimpleReferenceTuple)this.CreateRow(sourceLineNumbers, TupleDefinitionType.WixSimpleReference);
wixSimpleReferenceRow.Table = tableName;
wixSimpleReferenceRow.PrimaryKeys = joinedKeys;
}
}
}
///
/// A row in the WixGroup table is added for this child node and its parent node.
///
/// Source line information for the row.
/// Type of child's complex reference parent.
/// Id of the parenet node.
/// Complex reference type of child
/// Id of the Child Node.
public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId)
{
if (!this.EncounteredError)
{
if (null == parentId || ComplexReferenceParentType.Unknown == parentType)
{
return;
}
if (null == childId)
{
throw new ArgumentNullException("childId");
}
var row = (WixGroupTuple)this.CreateRow(sourceLineNumbers, TupleDefinitionType.WixGroup);
row.ParentId = parentId;
row.ParentType = parentType;
row.ChildId = childId;
row.ChildType = childType;
}
}
///
/// Add the appropriate rows to make sure that the given table shows up
/// in the resulting output.
///
/// Source line numbers.
/// Name of the table to ensure existance of.
public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName)
{
if (!this.EncounteredError)
{
var row = this.CreateRow(sourceLineNumbers, TupleDefinitionType.WixEnsureTable);
row.Set(0, tableName);
// We don't add custom table definitions to the tableDefinitions collection,
// so if it's not in there, it better be a custom table. If the Id is just wrong,
// instead of a custom table, we get an unresolved reference at link time.
if (!this.creator.TryGetTupleDefinitionByName(tableName, out var ignored))
{
this.CreateSimpleReference(sourceLineNumbers, "WixCustomTable", tableName);
}
}
}
///
/// Get an attribute value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.
/// The attribute's value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly)
{
return Common.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
}
///
/// Get a valid code page by web name or number from a string attribute.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// A valid code page integer value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
if (null == attribute)
{
throw new ArgumentNullException("attribute");
}
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
try
{
int codePage = Common.GetValidCodePage(value);
return codePage;
}
catch (NotSupportedException)
{
this.OnMessage(WixErrors.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
}
return CompilerConstants.IllegalInteger;
}
///
/// Get a valid code page by web name or number from a string attribute.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// Whether to allow Unicode (UCS) or UTF code pages.
/// A valid code page integer value or variable expression.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false)
{
if (null == attribute)
{
throw new ArgumentNullException("attribute");
}
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
// allow for localization of code page names and values
if (IsValidLocIdentifier(value))
{
return value;
}
try
{
int codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers);
return codePage.ToString(CultureInfo.InvariantCulture);
}
catch (NotSupportedException)
{
// not a valid windows code page
this.OnMessage(WixErrors.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
}
catch (WixException e)
{
this.OnMessage(e.Error);
}
return null;
}
///
/// Get an integer attribute value and displays an error for an illegal integer value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The minimum legal value.
/// The maximum legal value.
/// The attribute's integer value or a special value if an error occurred during conversion.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
{
return Common.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum);
}
///
/// Get a long integral attribute value and displays an error for an illegal long value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The minimum legal value.
/// The maximum legal value.
/// The attribute's long value or a special value if an error occurred during conversion.
public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum)
{
Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing.");
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
try
{
long longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat);
if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue)
{
this.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, longValue));
}
else if (minimum > longValue || maximum < longValue)
{
this.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum));
longValue = CompilerConstants.IllegalLong;
}
return longValue;
}
catch (FormatException)
{
this.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
catch (OverflowException)
{
this.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return CompilerConstants.IllegalLong;
}
///
/// Get a date time attribute value and display errors for illegal values.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// Int representation of the date time.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
if (null == attribute)
{
throw new ArgumentNullException("attribute");
}
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
try
{
DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat);
return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) +
(date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2);
}
catch (ArgumentOutOfRangeException)
{
this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
catch (FormatException)
{
this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
catch (OverflowException)
{
this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return CompilerConstants.IllegalInteger;
}
///
/// Get an integer attribute value or localize variable and displays an error for
/// an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The minimum legal value.
/// The maximum legal value.
/// The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion.
public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
{
if (null == attribute)
{
throw new ArgumentNullException("attribute");
}
Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing.");
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
if (IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value))
{
return value;
}
else
{
try
{
int integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat);
if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer)
{
this.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, integer));
}
else if (minimum > integer || maximum < integer)
{
this.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum));
integer = CompilerConstants.IllegalInteger;
}
return value;
}
catch (FormatException)
{
this.OnMessage(WixErrors.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
catch (OverflowException)
{
this.OnMessage(WixErrors.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
}
return null;
}
///
/// Get a guid attribute value and displays an error for an illegal guid value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// Determines whether the guid can be automatically generated.
/// If true, no error is raised on empty value. If false, an error is raised.
/// The attribute's guid value or a special value if an error occurred.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
[SuppressMessage("Microsoft.Performance", "CA1807:AvoidUnnecessaryStringCreation")]
public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false)
{
if (null == attribute)
{
throw new ArgumentNullException("attribute");
}
EmptyRule emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly;
string value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
if (String.IsNullOrEmpty(value) && canBeEmpty)
{
return String.Empty;
}
else if (!String.IsNullOrEmpty(value))
{
// If the value starts and ends with braces or parenthesis, accept that and strip them off.
if ((value.StartsWith("{", StringComparison.Ordinal) && value.EndsWith("}", StringComparison.Ordinal))
|| (value.StartsWith("(", StringComparison.Ordinal) && value.EndsWith(")", StringComparison.Ordinal)))
{
value = value.Substring(1, value.Length - 2);
}
try
{
Guid guid;
if (generatable && "*".Equals(value, StringComparison.Ordinal))
{
return value;
}
if (CompilerCore.PutGuidHere.IsMatch(value))
{
this.OnMessage(WixErrors.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
return CompilerConstants.IllegalGuid;
}
else if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal))
{
return value;
}
else
{
guid = new Guid(value);
}
string uppercaseGuid = guid.ToString().ToUpper(CultureInfo.InvariantCulture);
if (this.ShowPedanticMessages)
{
if (uppercaseGuid != value)
{
this.OnMessage(WixErrors.GuidContainsLowercaseLetters(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return String.Concat("{", uppercaseGuid, "}");
}
catch (FormatException)
{
this.OnMessage(WixErrors.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return CompilerConstants.IllegalGuid;
}
///
/// Get an identifier attribute value and displays an error for an illegal identifier value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's identifier value or a special value if an error occurred.
public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = Common.GetAttributeValue(sourceLineNumbers, attribute, EmptyRule.CanBeEmpty);
AccessModifier access = AccessModifier.Public;
Match match = CompilerCore.LegalIdentifierWithAccess.Match(value);
if (!match.Success)
{
return null;
}
else if (match.Groups["access"].Success)
{
access = (AccessModifier)Enum.Parse(typeof(AccessModifier), match.Groups["access"].Value, true);
}
value = match.Groups["id"].Value;
if (Common.IsIdentifier(value) && 72 < value.Length)
{
this.OnMessage(WixWarnings.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
return new Identifier(value, access);
}
///
/// Get an identifier attribute value and displays an error for an illegal identifier value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's identifier value or a special value if an error occurred.
public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
return Common.GetAttributeIdentifierValue(sourceLineNumbers, attribute);
}
///
/// Gets a yes/no value and displays an error for an illegal yes/no value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's YesNoType value.
public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
YesNoType result = YesNoType.IllegalValue;
if (value.Equals("yes") || value.Equals("true"))
{
result = YesNoType.Yes;
}
else if (value.Equals("no") || value.Equals("false"))
{
result = YesNoType.No;
}
else
{
this.OnMessage(WixErrors.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
return result;
}
///
/// Gets a yes/no/default value and displays an error for an illegal yes/no value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's YesNoDefaultType value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
switch (Wix.Enums.ParseYesNoDefaultType(value))
{
case Wix.YesNoDefaultType.@default:
return YesNoDefaultType.Default;
case Wix.YesNoDefaultType.no:
return YesNoDefaultType.No;
case Wix.YesNoDefaultType.yes:
return YesNoDefaultType.Yes;
case Wix.YesNoDefaultType.NotSet:
// Previous code never returned 'NotSet'!
break;
default:
this.OnMessage(WixErrors.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
break;
}
}
return YesNoDefaultType.IllegalValue;
}
///
/// Gets a yes/no/always value and displays an error for an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's YesNoAlwaysType value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public YesNoAlwaysType GetAttributeYesNoAlwaysValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
switch (Wix.Enums.ParseYesNoAlwaysType(value))
{
case Wix.YesNoAlwaysType.@always:
return YesNoAlwaysType.Always;
case Wix.YesNoAlwaysType.no:
return YesNoAlwaysType.No;
case Wix.YesNoAlwaysType.yes:
return YesNoAlwaysType.Yes;
case Wix.YesNoAlwaysType.NotSet:
// Previous code never returned 'NotSet'!
break;
default:
this.OnMessage(WixErrors.IllegalYesNoAlwaysValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
break;
}
}
return YesNoAlwaysType.IllegalValue;
}
///
/// Gets a short filename value and displays an error for an illegal short filename value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// true if wildcards are allowed in the filename.
/// The attribute's short filename value.
public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards)
{
if (null == attribute)
{
throw new ArgumentNullException("attribute");
}
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
if (!this.IsValidShortFilename(value, allowWildcards) && !this.IsValidLocIdentifier(value))
{
this.OnMessage(WixErrors.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
else if (CompilerCore.IsAmbiguousFilename(value))
{
this.OnMessage(WixWarnings.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return value;
}
///
/// Gets a long filename value and displays an error for an illegal long filename value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// true if wildcards are allowed in the filename.
/// true if relative paths are allowed in the filename.
/// The attribute's long filename value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false)
{
if (null == attribute)
{
throw new ArgumentNullException("attribute");
}
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value))
{
if (allowRelative)
{
this.OnMessage(WixErrors.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
else
{
this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
else if (allowRelative)
{
string normalizedPath = value.Replace('\\', '/');
if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../"))
{
this.OnMessage(WixErrors.PayloadMustBeRelativeToCache(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
else if (CompilerCore.IsAmbiguousFilename(value))
{
this.OnMessage(WixWarnings.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return value;
}
///
/// Gets a version value or possibly a binder variable and displays an error for an illegal version value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's version value.
public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (!String.IsNullOrEmpty(value))
{
try
{
return new Version(value).ToString();
}
catch (FormatException) // illegal integer in version
{
// Allow versions to contain binder variables.
if (Common.ContainsValidBinderVariable(value))
{
return value;
}
this.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
catch (ArgumentException)
{
this.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return null;
}
///
/// Gets a RegistryRoot value and displays an error for an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// Whether HKMU is considered a valid value.
/// The attribute's RegisitryRootType value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public Wix.RegistryRootType GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
{
Wix.RegistryRootType registryRoot = Wix.RegistryRootType.NotSet;
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
registryRoot = Wix.Enums.ParseRegistryRootType(value);
if (Wix.RegistryRootType.IllegalValue == registryRoot || (!allowHkmu && Wix.RegistryRootType.HKMU == registryRoot))
{
// TODO: Find a way to expose the valid values programatically!
if (allowHkmu)
{
this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
"HKMU", "HKCR", "HKCU", "HKLM", "HKU"));
}
else
{
this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
"HKCR", "HKCU", "HKLM", "HKU"));
}
}
}
return registryRoot;
}
///
/// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// Whether HKMU is returned as -1 (true), or treated as an error (false).
/// The attribute's RegisitryRootType value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public int GetAttributeMsidbRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
{
Wix.RegistryRootType registryRoot = this.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu);
switch (registryRoot)
{
case Wix.RegistryRootType.NotSet:
return CompilerConstants.IntegerNotSet;
case Wix.RegistryRootType.HKCR:
return Core.Native.MsiInterop.MsidbRegistryRootClassesRoot;
case Wix.RegistryRootType.HKCU:
return Core.Native.MsiInterop.MsidbRegistryRootCurrentUser;
case Wix.RegistryRootType.HKLM:
return Core.Native.MsiInterop.MsidbRegistryRootLocalMachine;
case Wix.RegistryRootType.HKU:
return Core.Native.MsiInterop.MsidbRegistryRootUsers;
case Wix.RegistryRootType.HKMU:
// This is gross, but there was *one* registry root parsing instance
// (in Compiler.ParseRegistrySearchElement()) that did not explicitly
// handle HKMU and it fell through to the default error case. The
// others treated it as -1, which is what we do here.
if (allowHkmu)
{
return -1;
}
break;
}
return CompilerConstants.IntegerNotSet;
}
///
/// Gets an InstallUninstallType value and displays an error for an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's InstallUninstallType value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public Wix.InstallUninstallType GetAttributeInstallUninstallValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
Wix.InstallUninstallType installUninstall = Wix.InstallUninstallType.NotSet;
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
installUninstall = Wix.Enums.ParseInstallUninstallType(value);
if (Wix.InstallUninstallType.IllegalValue == installUninstall)
{
// TODO: Find a way to expose the valid values programatically!
this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
"install", "uninstall", "both"));
}
}
return installUninstall;
}
///
/// Gets an ExitType value and displays an error for an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's ExitType value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public Wix.ExitType GetAttributeExitValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
Wix.ExitType result = Wix.ExitType.NotSet;
if (!Enum.TryParse(value, out result))
{
result = Wix.ExitType.IllegalValue;
// TODO: Find a way to expose the valid values programatically!
this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
"success", "cancel", "error", "suspend"));
}
return result;
}
///
/// Gets a Bundle variable value and displays an error for an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (!String.IsNullOrEmpty(value))
{
if (CompilerCore.BuiltinBundleVariables.Contains(value))
{
string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables);
this.OnMessage(WixErrors.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues));
}
}
return value;
}
///
/// Gets an MsiProperty name value and displays an error for an illegal value.
///
/// Source line information about the owner element.
/// The attribute containing the value to get.
/// The attribute's value.
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
{
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (0 < value.Length)
{
if (CompilerCore.DisallowedMsiProperties.Contains(value))
{
string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties);
this.OnMessage(WixErrors.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues));
}
}
return value;
}
///
/// Checks if the string contains a property (i.e. "foo[Property]bar")
///
/// String to evaluate for properties.
/// True if a property is found in the string.
public bool ContainsProperty(string possibleProperty)
{
return Common.ContainsProperty(possibleProperty);
}
///
/// Generate an identifier by hashing data from the row.
///
/// Three letter or less prefix for generated row identifier.
/// Information to hash.
/// The generated identifier.
[SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
public Identifier CreateIdentifier(string prefix, params string[] args)
{
string id = Common.GenerateIdentifier(prefix, args);
return new Identifier(id, AccessModifier.Private);
}
///
/// Create an identifier based on passed file name
///
/// File name to generate identifer from
///
public Identifier CreateIdentifierFromFilename(string filename)
{
string id = Common.GetIdentifierFromName(filename);
return new Identifier(id, AccessModifier.Private);
}
///
/// Attempts to use an extension to parse the attribute.
///
/// Element containing attribute to be parsed.
/// Attribute to be parsed.
/// Extra information about the context in which this element is being parsed.
public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary context = null)
{
// Ignore attributes defined by the W3C because we'll assume they are always right.
if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))
{
return;
}
if (this.TryFindExtension(attribute.Name.NamespaceName, out var extension))
{
extension.ParseAttribute(element, attribute, context);
}
else
{
SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
this.OnMessage(WixErrors.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName));
}
}
///
/// Attempts to use an extension to parse the element.
///
/// Element containing element to be parsed.
/// Element to be parsed.
/// Extra information about the context in which this element is being parsed.
public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary context = null)
{
if (this.TryFindExtension(element.Name.Namespace, out var extension))
{
SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(parentElement);
extension.ParseElement(parentElement, element, context);
}
else
{
SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
this.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
}
}
///
/// Process all children of the element looking for extensions and erroring on the unexpected.
///
/// Element to parse children.
public void ParseForExtensionElements(XElement element)
{
foreach (XElement child in element.Elements())
{
if (element.Name.Namespace == child.Name.Namespace)
{
this.UnexpectedElement(element, child);
}
else
{
this.ParseExtensionElement(element, child);
}
}
}
///
/// Attempts to use an extension to parse the element, with support for setting component keypath.
///
/// Element containing element to be parsed.
/// Element to be parsed.
/// Extra information about the context in which this element is being parsed.
public ComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary context)
{
ComponentKeyPath keyPath = null;
ICompilerExtension extension;
if (this.TryFindExtension(element.Name.Namespace, out extension))
{
keyPath = extension.ParsePossibleKeyPathElement(parentElement, element, context);
}
else
{
SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
this.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
}
return keyPath;
}
///
/// Displays an unexpected attribute error if the attribute is not the namespace attribute.
///
/// Element containing unexpected attribute.
/// The unexpected attribute.
public void UnexpectedAttribute(XElement element, XAttribute attribute)
{
SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
Common.UnexpectedAttribute(sourceLineNumbers, attribute);
}
///
/// Display an unexepected element error.
///
/// The parent element.
/// The unexpected child element.
public void UnexpectedElement(XElement parentElement, XElement childElement)
{
SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement);
this.OnMessage(WixErrors.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName));
}
///
/// Sends a message to the message delegate if there is one.
///
/// Message event arguments.
public void OnMessage(MessageEventArgs e)
{
Messaging.Instance.OnMessage(e);
}
///
/// Verifies that the calling assembly version is equal to or newer than the given .
///
/// Source line information about the owner element.
/// The version required of the calling assembly.
internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion)
{
// an null or empty string means any version will work
if (!string.IsNullOrEmpty(requiredVersion))
{
Assembly caller = Assembly.GetCallingAssembly();
AssemblyName name = caller.GetName();
FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location);
Version versionRequired = new Version(requiredVersion);
Version versionCurrent = new Version(fv.FileVersion);
if (versionRequired > versionCurrent)
{
if (this.GetType().Assembly.Equals(caller))
{
this.OnMessage(WixErrors.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired));
}
else
{
this.OnMessage(WixErrors.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name));
}
}
}
}
///
/// Creates a new section and makes it the active section in the core.
///
/// Unique identifier for the section.
/// Type of section to create.
/// Codepage for the resulting database for this ection.
/// New section.
internal IntermediateSection CreateActiveSection(string id, SectionType type, int codepage, string compilationId)
{
this.ActiveSection = this.CreateSection(id, type, codepage, compilationId);
this.activeSectionInlinedDirectoryIds = new HashSet();
this.activeSectionSimpleReferences = new HashSet();
return this.ActiveSection;
}
///
/// Creates a new section.
///
/// Unique identifier for the section.
/// Type of section to create.
/// Codepage for the resulting database for this ection.
/// New section.
internal IntermediateSection CreateSection(string id, SectionType type, int codepage, string compilationId)
{
var section = new IntermediateSection(id, type, codepage);
section.CompilationId = compilationId;
this.intermediate.Sections.Add(section);
return section;
}
///
/// Creates a WiX complex reference in the active section.
///
/// Source line information.
/// The parent type.
/// The parent id.
/// The parent language.
/// The child type.
/// The child id.
/// Whether the child is primary.
internal void CreateWixComplexReferenceRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
{
if (!this.EncounteredError)
{
var wixComplexReferenceRow = (WixComplexReferenceTuple)this.CreateRow(sourceLineNumbers, TupleDefinitionType.WixComplexReference);
wixComplexReferenceRow.Parent = parentId;
wixComplexReferenceRow.ParentType = parentType;
wixComplexReferenceRow.ParentLanguage = parentLanguage;
wixComplexReferenceRow.Child = childId;
wixComplexReferenceRow.ChildType = childType;
wixComplexReferenceRow.IsPrimary = isPrimary;
}
}
///
/// Creates WixComplexReference and WixGroup rows in the active section.
///
/// Source line information.
/// The parent type.
/// The parent id.
/// The parent language.
/// The child type.
/// The child id.
/// Whether the child is primary.
public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
{
this.CreateWixComplexReferenceRow(sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary);
this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, childType, childId);
}
///
/// Creates a directory row from a name.
///
/// Source line information.
/// Optional identifier for the new row.
/// Optional identifier for the parent row.
/// Long name of the directory.
/// Optional short name of the directory.
/// Optional source name for the directory.
/// Optional short source name for the directory.
/// Identifier for the newly created row.
internal Identifier CreateDirectoryRow(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
{
string defaultDir = null;
if (name.Equals("SourceDir") || this.IsValidShortFilename(name, false))
{
defaultDir = name;
}
else
{
if (String.IsNullOrEmpty(shortName))
{
shortName = this.CreateShortName(name, false, false, "Directory", parentId);
}
defaultDir = String.Concat(shortName, "|", name);
}
if (!String.IsNullOrEmpty(sourceName))
{
if (this.IsValidShortFilename(sourceName, false))
{
defaultDir = String.Concat(defaultDir, ":", sourceName);
}
else
{
if (String.IsNullOrEmpty(shortSourceName))
{
shortSourceName = this.CreateShortName(sourceName, false, false, "Directory", parentId);
}
defaultDir = String.Concat(defaultDir, ":", shortSourceName, "|", sourceName);
}
}
// For anonymous directories, create the identifier. If this identifier already exists in the
// active section, bail so we don't add duplicate anonymous directory rows (which are legal
// but bloat the intermediate and ultimately make the linker do "busy work").
if (null == id)
{
id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
if (!this.activeSectionInlinedDirectoryIds.Add(id.Id))
{
return id;
}
}
var row = this.CreateRow(sourceLineNumbers, TupleDefinitionType.Directory, id);
row.Set(1, parentId);
row.Set(2, defaultDir);
return id;
}
///
/// Gets the attribute value as inline directory syntax.
///
/// Source line information.
/// Attribute containing the value to get.
/// Flag indicates whether the inline directory syntax should be processed to create a directory row or to create a directory reference.
/// Inline directory syntax split into array of strings or null if the syntax did not parse.
internal string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false)
{
string[] result = null;
string value = this.GetAttributeValue(sourceLineNumbers, attribute);
if (!String.IsNullOrEmpty(value))
{
int pathStartsAt = 0;
result = value.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
if (result[0].EndsWith(":", StringComparison.Ordinal))
{
string id = result[0].TrimEnd(':');
if (1 == result.Length)
{
this.OnMessage(WixErrors.InlineDirectorySyntaxRequiresPath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
return null;
}
else if (!this.IsValidIdentifier(id))
{
this.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
return null;
}
pathStartsAt = 1;
}
else if (resultUsedToCreateReference && 1 == result.Length)
{
if (value.EndsWith("\\"))
{
if (!this.IsValidLongFilename(result[0]))
{
this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
return null;
}
}
else if (!this.IsValidIdentifier(result[0]))
{
this.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
return null;
}
return result; // return early to avoid additional checks below.
}
// Check each part of the relative path to ensure that it is a valid directory name.
for (int i = pathStartsAt; i < result.Length; ++i)
{
if (!this.IsValidLongFilename(result[i]))
{
this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[i]));
return null;
}
}
if (1 < result.Length && !value.EndsWith("\\"))
{
this.OnMessage(WixWarnings.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
}
}
return result;
}
///
/// Finds a compiler extension by namespace URI.
///
/// Namespace the extension supports.
/// True if found compiler extension or false if nothing matches namespace URI.
private bool TryFindExtension(XNamespace ns, out ICompilerExtension extension)
{
return this.extensions.TryGetValue(ns, out extension);
}
private static string CreateValueList(ValueListKind kind, IEnumerable values)
{
// Ideally, we could denote the list kind (and the list itself) directly in the
// message XML, and detect and expand in the MessageHandler.GenerateMessageString()
// method. Doing so would make vararg-style messages much easier, but impacts
// every single message we format. For now, callers just have to know when a
// message takes a list of values in a single string argument, the caller will
// have to do the expansion themselves. (And, unfortunately, hard-code the knowledge
// that the list is an 'and' or 'or' list.)
// For a localizable solution, we need to be able to get the list format string
// from resources. We aren't currently localized right now, so the values are
// just hard-coded.
const string valueFormat = "'{0}'";
const string valueSeparator = ", ";
string terminalTerm = String.Empty;
switch (kind)
{
case ValueListKind.None:
terminalTerm = "";
break;
case ValueListKind.And:
terminalTerm = "and ";
break;
case ValueListKind.Or:
terminalTerm = "or ";
break;
}
StringBuilder list = new StringBuilder();
// This weird construction helps us determine when we're adding the last value
// to the list. Instead of adding them as we encounter them, we cache the current
// value and append the *previous* one.
string previousValue = null;
bool haveValues = false;
foreach (string value in values)
{
if (null != previousValue)
{
if (haveValues)
{
list.Append(valueSeparator);
}
list.AppendFormat(valueFormat, previousValue);
haveValues = true;
}
previousValue = value;
}
// If we have no previous value, that means that the list contained no values, and
// something has gone very wrong.
Debug.Assert(null != previousValue);
if (null != previousValue)
{
if (haveValues)
{
list.Append(valueSeparator);
list.Append(terminalTerm);
}
list.AppendFormat(valueFormat, previousValue);
haveValues = true;
}
return list.ToString();
}
}
}