From d3d3649a68cb1fa589fdd987a6690dbd5d671f0d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 17 Sep 2017 15:35:20 -0700 Subject: Initial code commit --- src/WixToolset.Core/PatchTransform.cs | 274 ++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 src/WixToolset.Core/PatchTransform.cs (limited to 'src/WixToolset.Core/PatchTransform.cs') diff --git a/src/WixToolset.Core/PatchTransform.cs b/src/WixToolset.Core/PatchTransform.cs new file mode 100644 index 00000000..c87b1a21 --- /dev/null +++ b/src/WixToolset.Core/PatchTransform.cs @@ -0,0 +1,274 @@ +// 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.Globalization; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using WixToolset.Extensibility; + + public class PatchTransform : IMessageHandler + { + private string baseline; + private Output transform; + private string transformPath; + + public string Baseline + { + get { return this.baseline; } + } + + public Output Transform + { + get + { + if (null == this.transform) + { + this.transform = Output.Load(this.transformPath, false); + } + + return this.transform; + } + } + + public string TransformPath + { + get { return this.transformPath; } + } + + public PatchTransform(string transformPath, string baseline) + { + this.transformPath = transformPath; + this.baseline = baseline; + } + + /// + /// Validates that the differences in the transform are valid for patch transforms. + /// + public void Validate() + { + // Changing the ProdocutCode in a patch transform is not recommended. + Table propertyTable = this.Transform.Tables["Property"]; + if (null != propertyTable) + { + foreach (Row row in propertyTable.Rows) + { + // Only interested in modified rows; fast check. + if (RowOperation.Modify == row.Operation) + { + if (0 == String.CompareOrdinal("ProductCode", (string)row[0])) + { + this.OnMessage(WixWarnings.MajorUpgradePatchNotRecommended()); + } + } + } + } + + // If there is nothing in the component table we can return early because the remaining checks are component based. + Table componentTable = this.Transform.Tables["Component"]; + if (null == componentTable) + { + return; + } + + // Index Feature table row operations + Table featureTable = this.Transform.Tables["Feature"]; + Table featureComponentsTable = this.Transform.Tables["FeatureComponents"]; + Hashtable featureOps = null; + if (null != featureTable) + { + int capacity = featureTable.Rows.Count; + featureOps = new Hashtable(capacity); + + foreach (Row row in featureTable.Rows) + { + featureOps[(string)row[0]] = row.Operation; + } + } + else + { + featureOps = new Hashtable(); + } + + // Index Component table and check for keypath modifications + Hashtable deletedComponent = new Hashtable(); + Hashtable componentKeyPath = new Hashtable(); + foreach (Row row in componentTable.Rows) + { + string id = row.Fields[0].Data.ToString(); + string keypath = (null == row.Fields[5].Data) ? String.Empty : row.Fields[5].Data.ToString(); + + componentKeyPath.Add(id, keypath); + if (RowOperation.Delete == row.Operation) + { + deletedComponent.Add(id, row); + } + else if (RowOperation.Modify == row.Operation) + { + if (row.Fields[1].Modified) + { + // Changing the guid of a component is equal to deleting the old one and adding a new one. + deletedComponent.Add(id, row); + } + + // If the keypath is modified its an error + if (row.Fields[5].Modified) + { + this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, id, this.transformPath)); + } + } + } + + // Verify changes in the file table + Table fileTable = this.Transform.Tables["File"]; + if (null != fileTable) + { + Hashtable componentWithChangedKeyPath = new Hashtable(); + foreach (Row row in fileTable.Rows) + { + if (RowOperation.None != row.Operation) + { + string fileId = row.Fields[0].Data.ToString(); + string componentId = row.Fields[1].Data.ToString(); + + // If this file is the keypath of a component + if (String.Equals((string)componentKeyPath[componentId], fileId, StringComparison.Ordinal)) + { + if (row.Fields[2].Modified) + { + // You cant change the filename of a file that is the keypath of a component. + this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, componentId, this.transformPath)); + } + + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + + if (RowOperation.Delete == row.Operation) + { + // If the file is removed from a component that is not deleted. + if (!deletedComponent.ContainsKey(componentId)) + { + bool foundRemoveFileEntry = false; + string filename = Msi.Installer.GetName((string)row[2], false, true); + + Table removeFileTable = this.Transform.Tables["RemoveFile"]; + if (null != removeFileTable) + { + foreach (Row removeFileRow in removeFileTable.Rows) + { + if (RowOperation.Delete == removeFileRow.Operation) + { + continue; + } + + if (componentId == (string)removeFileRow[1]) + { + // Check if there is a RemoveFile entry for this file + if (null != removeFileRow[2]) + { + string removeFileName = Msi.Installer.GetName((string)removeFileRow[2], false, true); + + // Convert the MSI format for a wildcard string to Regex format. + removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); + + Regex regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + if (regex.IsMatch(filename)) + { + foundRemoveFileEntry = true; + break; + } + } + } + } + } + + if (!foundRemoveFileEntry) + { + this.OnMessage(WixWarnings.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); + } + } + } + } + } + } + + if (0 < deletedComponent.Count) + { + // Index FeatureComponents table. + Hashtable featureComponents = new Hashtable(); + + if (null != featureComponentsTable) + { + foreach (Row row in featureComponentsTable.Rows) + { + ArrayList features; + string componentId = row.Fields[1].Data.ToString(); + + if (featureComponents.Contains(componentId)) + { + features = (ArrayList)featureComponents[componentId]; + } + else + { + features = new ArrayList(); + featureComponents.Add(componentId, features); + } + features.Add(row.Fields[0].Data.ToString()); + } + } + + // Check to make sure if a component was deleted, the feature was too. + foreach (DictionaryEntry entry in deletedComponent) + { + if (featureComponents.Contains(entry.Key.ToString())) + { + ArrayList features = (ArrayList)featureComponents[entry.Key.ToString()]; + foreach (string featureId in features) + { + if (!featureOps.ContainsKey(featureId) || RowOperation.Delete != (RowOperation)featureOps[featureId]) + { + // The feature was not deleted. + this.OnMessage(WixErrors.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, this.transformPath)); + } + } + } + } + } + + // Warn if new components are added to existing features + if (null != featureComponentsTable) + { + foreach (Row row in featureComponentsTable.Rows) + { + if (RowOperation.Add == row.Operation) + { + // Check if the feature is in the Feature table + string feature_ = (string)row[0]; + string component_ = (string)row[1]; + + // Features may not be present if not referenced + if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) + { + this.OnMessage(WixWarnings.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, this.transformPath)); + } + } + } + } + } + + /// + /// Sends a message to the message delegate if there is one. + /// + /// Message event arguments. + public void OnMessage(MessageEventArgs e) + { + Messaging.Instance.OnMessage(e); + } + } +} -- cgit v1.2.3-55-g6feb