// 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);
}
}
}
}
}
}