// 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.Core { using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Text; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; 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 class CompilerCore { internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; // 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 readonly Dictionary extensions; private readonly IParseHelper parseHelper; private readonly Intermediate intermediate; private readonly IMessaging messaging; private Dictionary activeSectionCachedInlinedDirectoryIds; private HashSet activeSectionSimpleReferences; /// /// Constructor for all compiler core. /// /// The Intermediate object representing compiled source document. /// /// /// The WiX extensions collection. internal CompilerCore(Intermediate intermediate, IMessaging messaging, IParseHelper parseHelper, Dictionary extensions) { this.extensions = extensions; this.parseHelper = parseHelper; this.intermediate = intermediate; this.messaging = messaging; } /// /// 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 whether the compiler core encountered an error while processing. /// /// Flag if core encountered an error during processing. public bool EncounteredError => this.messaging.EncounteredError; /// /// Gets or sets the option to show pedantic messages. /// /// The option to show pedantic messages. public bool ShowPedanticMessages { get; set; } /// /// Add a symbol to the active section. /// /// Symbol to add. public T AddSymbol(T symbol) where T : IntermediateSymbol { return this.ActiveSection.AddSymbol(symbol); } /// /// 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; } internal void InnerTextDisallowed(XElement element) { this.parseHelper.InnerTextDisallowed(element); } /// /// Verifies that a filename is ambiguous. /// /// Filename to verify. /// true if the filename is ambiguous; false otherwise. public static bool IsAmbiguousFilename(string filename) { if (String.IsNullOrEmpty(filename)) { return false; } var tilde = filename.IndexOf('~'); return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]); } /// /// 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 this.parseHelper.IsValidIdentifier(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) { return this.parseHelper.IsValidLocIdentifier(identifier); } /// /// 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) { return this.parseHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); } /// /// 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 this.parseHelper.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, char replace) { if (String.IsNullOrEmpty(filename)) { return filename; } StringBuilder sb = null; var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters); while (found != -1) { if (sb == null) { sb = new StringBuilder(filename); } sb[found] = replace; found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1; } return sb?.ToString() ?? filename; } /// /// 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.IsValidFourPartVersion(version); } /// /// Creates group and ordering information. /// /// Source line numbers. /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of this item. /// Identifier for this item. /// Type of previous item, if known. /// Identifier of previous item, if known public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType type, string id, ComplexReferenceChildType previousType, string previousId) { if (this.EncounteredError) { return; } if (parentType != ComplexReferenceParentType.Unknown && parentId != null) { this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); } if (previousType != ComplexReferenceChildType.Unknown && previousId != null) { // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? // TODO: Also, we could potentially include an 'Attributes' field to track things like // 'before' vs. 'after', and explicit vs. inferred dependencies. this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) { ItemType = type, ItemIdRef = id, DependsOnType = previousType, DependsOnIdRef = previousId, }); } } /// /// 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 this.parseHelper.CreateGuid(namespaceGuid, value); } /// /// Creates directories using the inline directory syntax. /// /// Source line information. /// Optional identifier of parent directory. /// Optional inline syntax to override attribute's value. /// Identifier of the leaf directory created. public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null) { return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); } /// /// 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, RegistryRootType root, string key, string name, string value, string componentId) { return this.parseHelper.CreateRegistrySymbol(this.ActiveSection, sourceLineNumbers, root, key, name, value, componentId, true); } /// /// Create a WixSimpleReferenceSymbol in the active section. /// /// Source line information for the row. /// The symbol name of the simple reference. /// The primary key of the simple reference. public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) { if (!this.EncounteredError) { var id = String.Concat(symbolName, ":", primaryKey); // If this simple reference hasn't been added to the active section already, add it. if (this.activeSectionSimpleReferences.Add(id)) { this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKey); } } } /// /// Create a WixSimpleReferenceSymbol in the active section. /// /// Source line information for the row. /// The symbol name of the simple reference. /// The primary keys of the simple reference. public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) { if (!this.EncounteredError) { var joinedKeys = String.Join("/", primaryKeys); var id = String.Concat(symbolName, ":", joinedKeys); // If this simple reference hasn't been added to the active section already, add it. if (this.activeSectionSimpleReferences.Add(id)) { this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKeys); } } } /// /// Create a WixSimpleReferenceSymbol in the active section. /// /// Source line information for the row. /// The symbol definition of the simple reference. /// The primary key of the simple reference. public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) { this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKey); } /// /// Create a WixSimpleReferenceSymbol in the active section. /// /// Source line information for the row. /// The symbol definition of the simple reference. /// The primary keys of the simple reference. public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) { this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKeys); } /// /// 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) { this.parseHelper.CreateWixGroupSymbol(this.ActiveSection, sourceLineNumbers, parentType, parentId, childType, childId); } } /// /// Add the appropriate symbols 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) { this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableName); } } /// /// Add the appropriate symbols to make sure that the given table shows up /// in the resulting output. /// /// Source line numbers. /// Definition of the table to ensure existance of. public void EnsureTable(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) { if (!this.EncounteredError) { this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableDefinition); } } /// /// 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. public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) { return this.parseHelper.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. public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) { if (null == attribute) { throw new ArgumentNullException(nameof(attribute)); } var value = this.GetAttributeValue(sourceLineNumbers, attribute); try { return Common.GetValidCodePage(value); } catch (NotSupportedException) { this.Write(ErrorMessages.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. public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false) { if (null == attribute) { throw new ArgumentNullException(nameof(attribute)); } var value = this.GetAttributeValue(sourceLineNumbers, attribute); // Allow for localization of code page names and values. if (this.IsValidLocIdentifier(value)) { return value; } try { var codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers); return codePage.ToString(CultureInfo.InvariantCulture); } catch (NotSupportedException) { // Not a valid windows code page. this.messaging.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); } catch (WixException e) { this.messaging.Write(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. public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) { return this.parseHelper.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) { return this.parseHelper.GetAttributeLongValue(sourceLineNumbers, attribute, minimum, maximum); } /// /// 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. 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.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); } catch (FormatException) { this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); } catch (OverflowException) { this.Write(ErrorMessages.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."); var value = this.GetAttributeValue(sourceLineNumbers, attribute); if (0 < value.Length) { if (this.IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value)) { return value; } else { try { var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) { this.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); } else if (minimum > integer || maximum < integer) { this.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); integer = CompilerConstants.IllegalInteger; } return value; } catch (FormatException) { this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); } catch (OverflowException) { this.Write(ErrorMessages.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. public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) { return this.parseHelper.GetAttributeGuidValue(sourceLineNumbers, attribute, generatable, canBeEmpty); } /// /// 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) { return this.parseHelper.GetAttributeIdentifier(sourceLineNumbers, attribute); } /// /// 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 this.parseHelper.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) { return this.parseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute); } /// /// 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. public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) { return this.parseHelper.GetAttributeYesNoDefaultValue(sourceLineNumbers, attribute); } /// /// 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 = false) { if (null == attribute) { throw new ArgumentNullException("attribute"); } var value = this.GetAttributeValue(sourceLineNumbers, attribute); if (0 < value.Length) { if (!this.parseHelper.IsValidShortFilename(value, allowWildcards) && !Common.ContainsValidBinderVariable(value)) { this.Write(ErrorMessages.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); } else if (CompilerCore.IsAmbiguousFilename(value)) { this.Write(WarningMessages.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. public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false) { return this.parseHelper.GetAttributeLongFilename(sourceLineNumbers, attribute, allowWildcards, allowRelative); } /// /// 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) { return this.parseHelper.GetAttributeVersionValue(sourceLineNumbers, attribute); } /// /// 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. public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) { return this.parseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu); } /// /// 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. 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.Write(ErrorMessages.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. 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.Write(ErrorMessages.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 this.parseHelper.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. public Identifier CreateIdentifier(string prefix, params string[] args) { return this.parseHelper.CreateIdentifier(prefix, args); } /// /// Create an identifier based on passed file name /// /// File name to generate identifer from /// public Identifier CreateIdentifierFromFilename(string filename) { return this.parseHelper.CreateIdentifierFromFilename(filename); } /// /// 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) { this.parseHelper.ParseExtensionAttribute(this.extensions.Values, this.intermediate, this.ActiveSection, element, attribute, context); } /// /// 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) { this.parseHelper.ParseExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); } /// /// Process all children of the element looking for extensions and erroring on the unexpected. /// /// Element to parse children. public void ParseForExtensionElements(XElement element) { this.parseHelper.ParseForExtensionElements(this.extensions.Values, this.intermediate, this.ActiveSection, element); } /// /// 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 IComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary context) { return this.parseHelper.ParsePossibleKeyPathExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); } /// /// 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) { this.parseHelper.UnexpectedAttribute(element, attribute); } /// /// Display an unexepected element error. /// /// The parent element. /// The unexpected child element. public void UnexpectedElement(XElement parentElement, XElement childElement) { this.parseHelper.UnexpectedElement(parentElement, childElement); } /// /// Sends a message. /// /// Message to write. public void Write(Message message) { this.messaging.Write(message); } /// /// 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.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired)); } else { this.Write(ErrorMessages.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. /// Unique identifier for the compilation. /// New section. internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId) { this.ActiveSection = this.CreateSection(id, type, compilationId); this.activeSectionCachedInlinedDirectoryIds = new Dictionary(); this.activeSectionSimpleReferences = new HashSet(); return this.ActiveSection; } /// /// Creates a new section. /// /// Unique identifier for the section. /// Type of section to create. /// Unique identifier for the compilation. /// New section. internal IntermediateSection CreateSection(string id, SectionType type, string compilationId) { var section = new IntermediateSection(id, type, compilationId); this.intermediate.Sections.Add(section); return section; } /// /// 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.parseHelper.CreateComplexReference(this.ActiveSection, sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary); } /// /// 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 CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) { return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName); } public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) { this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); } internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) { return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); } 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(); } } }