// 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.WindowsInstaller.Bind { using System; using System.Collections.Generic; using System.IO; using System.Linq; using WixToolset.Data; using WixToolset.Data.Tuples; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; /// /// Set the guids for components with generatable guids. /// internal class CalculateComponentGuids { internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section) { this.Messaging = messaging; this.BackendHelper = helper; this.PathResolver = pathResolver; this.Section = section; } private IMessaging Messaging { get; } private IBackendHelper BackendHelper { get; } private IPathResolver PathResolver { get; } private IntermediateSection Section { get; } public void Execute() { Dictionary registryKeyRows = null; Dictionary targetPathsByDirectoryId = null; Dictionary componentIdGenSeeds = null; Dictionary> filesByComponentId = null; // Find components with generatable guids. foreach (var componentTuple in this.Section.Tuples.OfType()) { // Skip components that do not specify generate guid. if (componentTuple.ComponentId != "*") { continue; } if (String.IsNullOrEmpty(componentTuple.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentTuple.KeyPathType) { this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentTuple.SourceLineNumbers)); continue; } if (ComponentKeyPathType.Registry == componentTuple.KeyPathType) { if (registryKeyRows is null) { registryKeyRows = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); } if (registryKeyRows.TryGetValue(componentTuple.KeyPath, out var foundRow)) { var bitness = componentTuple.Win64 ? "64" : String.Empty; var regkey = String.Concat(bitness, foundRow.AsString(1), "\\", foundRow.AsString(2), "\\", foundRow.AsString(3)); componentTuple.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); } } else // must be a File KeyPath. { // If the directory table hasn't been loaded into an indexed hash // of directory ids to target names do that now. if (targetPathsByDirectoryId is null) { var directories = this.Section.Tuples.OfType().ToList(); targetPathsByDirectoryId = new Dictionary(directories.Count); // Get the target paths for all directories. foreach (var directory in directories) { // If the directory Id already exists, we will skip it here since // checking for duplicate primary keys is done later when importing tables // into database if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) { continue; } var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); } } // If the component id generation seeds have not been indexed // from the Directory tuples do that now. if (componentIdGenSeeds is null) { // If there are any Directory tuples, build up the Component Guid // generation seeds indexed by Directory/@Id. componentIdGenSeeds = this.Section.Tuples.OfType() .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); } // if the file rows have not been indexed by File.Component yet // then do that now if (filesByComponentId is null) { var files = this.Section.Tuples.OfType().ToList(); filesByComponentId = new Dictionary>(files.Count); foreach (var file in files) { if (!filesByComponentId.TryGetValue(file.ComponentRef, out var componentFiles)) { componentFiles = new List(); filesByComponentId.Add(file.ComponentRef, componentFiles); } componentFiles.Add(file); } } // validate component meets all the conditions to have a generated guid var currentComponentFiles = filesByComponentId[componentTuple.Id.Id]; var numFilesInComponent = currentComponentFiles.Count; string path = null; foreach (var fileRow in currentComponentFiles) { if (fileRow.Id.Id == componentTuple.KeyPath) { // calculate the key file's canonical target path string directoryPath = this.PathResolver.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentTuple.DirectoryRef, true); string fileName = Common.GetName(fileRow.Name, false, true).ToLowerInvariant(); path = Path.Combine(directoryPath, fileName); // find paths that are not canonicalized if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || path.StartsWith("TARGETDIR", StringComparison.Ordinal) || path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) { this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentTuple.SourceLineNumbers, fileRow.ComponentRef, path)); } // if component has more than one file, the key path must be versioned if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) { this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentTuple.SourceLineNumbers)); } } else { // not a key path, so it must be an unversioned file if component has more than one file if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) { this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentTuple.SourceLineNumbers)); } } } // if the rules were followed, reward with a generated guid if (!this.Messaging.EncounteredError) { componentTuple.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); } } } } } }