// 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.Bind;
using WixToolset.Core.Native;
using WixToolset.Data;
using WixToolset.Data.Tuples;
using WixToolset.Extensibility.Services;
///
/// Set the guids for components with generatable guids.
///
internal class CalculateComponentGuids
{
public CalculateComponentGuids(IMessaging messaging, IntermediateSection section)
{
this.Messaging = messaging;
this.Section = section;
}
private IMessaging Messaging { 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 componentRow in this.Section.Tuples.OfType())
{
// Skip components that do not specify generate guid.
if (componentRow.ComponentId != "*")
{
continue;
}
var odbcDataSourceKeyPath = (componentRow.Attributes & MsiInterop.MsidbComponentAttributesODBCDataSource) != 0;
if (String.IsNullOrEmpty(componentRow.KeyPath) || odbcDataSourceKeyPath)
{
this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers));
continue;
}
var registryKeyPath = (componentRow.Attributes & MsiInterop.MsidbComponentAttributesRegistryKeyPath) != 0;
if (registryKeyPath)
{
if (registryKeyRows is null)
{
registryKeyRows = this.Section.Tuples.OfType().ToDictionary(t => t.Registry);
}
if (registryKeyRows.TryGetValue(componentRow.KeyPath, out var foundRow))
{
var is64Bit = (componentRow.Attributes & MsiInterop.MsidbComponentAttributes64bit) != 0;
var bitness = is64Bit ? "64" : String.Empty;
var regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]);
componentRow.ComponentId = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant();
}
}
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 row 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(row.Directory))
{
continue;
}
var targetName = Common.GetName(row.DefaultDir, false, true);
targetPathsByDirectoryId.Add(row.Directory, new ResolvedDirectory(row.Directory_Parent, targetName));
}
}
// If the component id generation seeds have not been indexed
// from the WixDirectory table do that now.
if (componentIdGenSeeds is null)
{
// If there are any WixDirectory rows, 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.Directory_, 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.Component_, out var componentFiles))
{
componentFiles = new List();
filesByComponentId.Add(file.Component_, componentFiles);
}
componentFiles.Add(file);
}
}
// validate component meets all the conditions to have a generated guid
var currentComponentFiles = filesByComponentId[componentRow.Component];
var numFilesInComponent = currentComponentFiles.Count;
string path = null;
foreach (var fileRow in currentComponentFiles)
{
if (fileRow.File == componentRow.KeyPath)
{
// calculate the key file's canonical target path
string directoryPath = Binder.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentRow.Directory_, true);
string fileName = Common.GetName(fileRow.LongFileName, 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(componentRow.SourceLineNumbers, fileRow.Component_, 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(componentRow.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(componentRow.SourceLineNumbers));
}
}
}
// if the rules were followed, reward with a generated guid
if (!this.Messaging.EncounteredError)
{
componentRow.ComponentId = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant();
}
}
}
}
}
}