// 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.Rows; 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"; 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) 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); // 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 TableDefinitionCollection tableDefinitions; private Dictionary extensions; private Intermediate intermediate; private bool showPedanticMessages; private HashSet activeSectionInlinedDirectoryIds; private HashSet activeSectionSimpleReferences; /// /// Constructor for all compiler core. /// /// The Intermediate object representing compiled source document. /// The loaded table definition collection. /// The WiX extensions collection. internal CompilerCore(Intermediate intermediate, TableDefinitionCollection tableDefinitions, Dictionary extensions) { this.tableDefinitions = tableDefinitions; this.extensions = extensions; this.intermediate = intermediate; } /// /// Gets the section the compiler is currently emitting symbols into. /// /// The section the compiler is currently emitting symbols into. public Section 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 { get { return Messaging.Instance.EncounteredError; } } /// /// Gets or sets the option to show pedantic messages. /// /// The option to show pedantic messages. public bool ShowPedanticMessages { get { return this.showPedanticMessages; } set { this.showPedanticMessages = value; } } /// /// Gets the table definitions used by the compiler core. /// /// Table definition collection. public TableDefinitionCollection TableDefinitions { get { return this.tableDefinitions; } } /// /// 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 Row CreateRow(SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null) { return this.CreateRow(sourceLineNumbers, tableName, 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 Row CreateRow(SourceLineNumber sourceLineNumbers, string tableName, Section section, Identifier identifier = null) { TableDefinition tableDefinition = this.tableDefinitions[tableName]; Table table = section.EnsureTable(tableDefinition); Row row = table.CreateRow(sourceLineNumbers); if (null != identifier) { row.Access = identifier.Access; row[0] = identifier.Id; } 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) { Row patchReferenceRow = this.CreateRow(sourceLineNumbers, "WixPatchRef"); patchReferenceRow[0] = tableName; patchReferenceRow[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)); Row row = this.CreateRow(sourceLineNumbers, "Registry", id); row[1] = root; row[2] = key; row[3] = name; row[4] = value; row[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)) { WixSimpleReferenceRow wixSimpleReferenceRow = (WixSimpleReferenceRow)this.CreateRow(sourceLineNumbers, "WixSimpleReference"); wixSimpleReferenceRow.TableName = 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"); } WixGroupRow WixGroupRow = (WixGroupRow)this.CreateRow(sourceLineNumbers, "WixGroup"); WixGroupRow.ParentId = parentId; WixGroupRow.ParentType = parentType; WixGroupRow.ChildId = childId; WixGroupRow.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) { Row row = this.CreateRow(sourceLineNumbers, "WixEnsureTable"); row[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.tableDefinitions.Contains(tableName)) { 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; } ICompilerExtension extension; if (this.TryFindExtension(attribute.Name.NamespaceName, out 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) { ICompilerExtension extension; if (this.TryFindExtension(element.Name.Namespace, out 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 Section CreateActiveSection(string id, SectionType type, int codepage) { this.ActiveSection = this.CreateSection(id, type, codepage); 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 Section CreateSection(string id, SectionType type, int codepage) { Section newSection = new Section(id, type, codepage); this.intermediate.AddSection(newSection); return newSection; } /// /// 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) { WixComplexReferenceRow wixComplexReferenceRow = (WixComplexReferenceRow)this.CreateRow(sourceLineNumbers, "WixComplexReference"); wixComplexReferenceRow.ParentId = parentId; wixComplexReferenceRow.ParentType = parentType; wixComplexReferenceRow.ParentLanguage = parentLanguage; wixComplexReferenceRow.ChildId = 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; } } Row row = this.CreateRow(sourceLineNumbers, "Directory", id); row[1] = parentId; row[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(); } } }