Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect hash for ZIP installers in interactive update #528

Merged
merged 2 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/WingetCreateCLI/Commands/UpdateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ private async Task UpdateSingleInstallerInteractively(Installer installer)
string url = Prompt.Input<string>(Resources.NewInstallerUrl_Message, null, null, new[] { FieldValidation.ValidateProperty(newInstaller, nameof(Installer.InstallerUrl)) });

string packageFile = await DownloadPackageFile(url);
string archivePath = null;

if (string.IsNullOrEmpty(packageFile))
{
Expand All @@ -872,6 +873,7 @@ private async Task UpdateSingleInstallerInteractively(Installer installer)

if (packageFile.IsZipFile())
{
archivePath = packageFile;
string extractDirectory = ExtractArchiveAndRetrieveDirectoryPath(packageFile);
bool isRelativePathNull = false;

Expand All @@ -894,7 +896,7 @@ private async Task UpdateSingleInstallerInteractively(Installer installer)
packageFile = Path.Combine(extractDirectory, installer.NestedInstallerFiles.First().RelativeFilePath);
}

if (!PackageParser.ParsePackageAndUpdateInstallerNode(installer, packageFile, url))
if (!PackageParser.ParsePackageAndUpdateInstallerNode(installer, packageFile, url, archivePath))
{
Logger.ErrorLocalized(nameof(Resources.PackageParsing_Error), url);
Console.WriteLine();
Expand Down
21 changes: 14 additions & 7 deletions src/WingetCreateCore/Common/PackageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,15 +277,22 @@ public static void UpdateInstallerNodesAsync(List<InstallerMetadata> installerMe
/// Parses the package for relevant metadata and and updates the metadata of the provided installer node.
/// </summary>
/// <param name="installer">Installer node.</param>
/// <param name="path">Path to package file.</param>
/// <param name="filePath">Path to package file.</param>
/// <param name="url">Installer url.</param>
/// <param name="archivePath">Path to archive file containing the installer. Required if the installer type is Zip.</param>
/// <returns>Boolean indicating whether the package parse was successful.</returns>
public static bool ParsePackageAndUpdateInstallerNode(Installer installer, string path, string url)
public static bool ParsePackageAndUpdateInstallerNode(Installer installer, string filePath, string url, string archivePath = null)
{
// Guard clause to ensure that the archivePath is provided if the installer type is Zip.
if (installer.InstallerType == InstallerType.Zip && string.IsNullOrEmpty(archivePath))
{
return false;
}

List<Installer> newInstallers = new List<Installer>();
bool parseResult = ParseExeInstallerType(path, installer, newInstallers) ||
ParseMsix(path, installer, null, newInstallers) ||
ParseMsi(path, installer, null, newInstallers);
bool parseResult = ParseExeInstallerType(filePath, installer, newInstallers) ||
ParseMsix(filePath, installer, null, newInstallers) ||
ParseMsi(filePath, installer, null, newInstallers);

if (!parseResult || !newInstallers.Any())
{
Expand All @@ -302,11 +309,11 @@ public static bool ParsePackageAndUpdateInstallerNode(Installer installer, strin
else
{
// For a single installer, detect the architecture. If no architecture is detected, default to architecture from existing manifest.
newInstaller.Architecture = GetArchFromUrl(url) ?? GetMachineType(path)?.ToString().ToEnumOrDefault<Architecture>() ?? installer.Architecture;
newInstaller.Architecture = GetArchFromUrl(url) ?? GetMachineType(filePath)?.ToString().ToEnumOrDefault<Architecture>() ?? installer.Architecture;
}

newInstaller.InstallerUrl = url;
newInstaller.InstallerSha256 = GetFileHash(path);
newInstaller.InstallerSha256 = string.IsNullOrEmpty(archivePath) ? GetFileHash(filePath) : GetFileHash(archivePath);
UpdateInstallerMetadata(installer, newInstallers.First());
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

namespace Microsoft.WingetCreateUnitTests
{
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using AutoMapper;
using Microsoft.WingetCreateCore;
Expand Down Expand Up @@ -157,13 +159,47 @@ public void ParseAndUpdateInstaller()
WingetCreateCore.Models.Singleton.Installer initialInstaller = initialManifests.SingletonManifest.Installers.First();
Installer installer = ConvertSingletonInstaller(initialInstaller);

PackageParser.ParsePackageAndUpdateInstallerNode(installer, testMsiInstallerPath, installer.InstallerUrl);
bool result = PackageParser.ParsePackageAndUpdateInstallerNode(installer, testMsiInstallerPath, installer.InstallerUrl);
ClassicAssert.IsTrue(result, "ParsePackageAndUpdateInstallerNode should return true.");
ClassicAssert.AreEqual(InstallerType.Msi, installer.InstallerType, "InstallerType should be updated.");
ClassicAssert.AreEqual(initialInstaller.Architecture.ToEnumAttributeValue(), installer.Architecture.ToEnumAttributeValue(), "Architecture should not change.");
ClassicAssert.AreNotEqual(initialInstaller.InstallerSha256, installer.InstallerSha256, "InstallerSha256 should be updated.");
ClassicAssert.AreEqual("{E2650EFC-DCD3-4FAA-BBAC-FD1812B03A61}", installer.ProductCode, "ProductCode should be updated");
}

/// <summary>
/// Validates that the ParsePackageAndUpdateInstallerNode function works as expected for a zip installer.
/// </summary>
[Test]
public void ParseAndUpdateZipInstaller()
{
var testZipInstaller = TestUtils.MockDownloadFile(TestConstants.TestZipInstaller);
Assert.That(testZipInstaller, Is.Not.Null.And.Not.Empty);
string extractDirectory = Path.Combine(PackageParser.InstallerDownloadPath, Path.GetFileNameWithoutExtension(testZipInstaller));

try
{
ZipFile.ExtractToDirectory(testZipInstaller, extractDirectory, true);
}
catch (Exception e)
{
ClassicAssert.Fail($"Failed to extract the zip file: {e.Message}");
}

List<string> initialManifestContent = TestUtils.GetInitialManifestContent($"TestPublisher.ZipWithExe.yaml");
Manifests initialManifests = Serialization.DeserializeManifestContents(initialManifestContent);
WingetCreateCore.Models.Singleton.Installer initialInstaller = initialManifests.SingletonManifest.Installers.First();
Installer installer = ConvertSingletonInstaller(initialInstaller);
string nestedInstallerPath = Path.Combine(extractDirectory, installer.NestedInstallerFiles.First().RelativeFilePath);

bool result = PackageParser.ParsePackageAndUpdateInstallerNode(installer, nestedInstallerPath, installer.InstallerUrl, testZipInstaller);
ClassicAssert.IsTrue(result, "ParsePackageAndUpdateInstallerNode should return true.");
ClassicAssert.AreEqual(InstallerType.Zip, installer.InstallerType, "InstallerType should not change");
ClassicAssert.AreEqual(initialInstaller.Architecture.ToEnumAttributeValue(), installer.Architecture.ToEnumAttributeValue(), "Architecture should not change.");
ClassicAssert.AreNotEqual(initialInstaller.InstallerSha256, installer.InstallerSha256, "InstallerSha256 should be updated");
ClassicAssert.AreEqual(installer.InstallerSha256, PackageParser.GetFileHash(testZipInstaller), "InstallSha256 should match the hash of the zip file");
}

/// <summary>
/// Converts the SingletonManifest Installer object model to the InstallerManifest Installer object model.
/// </summary>
Expand All @@ -175,6 +211,7 @@ private static Installer ConvertSingletonInstaller(WingetCreateCore.Models.Singl
{
cfg.AllowNullCollections = true;
cfg.CreateMap<WingetCreateCore.Models.Singleton.Dependencies, WingetCreateCore.Models.Installer.Dependencies>();
cfg.CreateMap<WingetCreateCore.Models.Singleton.NestedInstallerFile, WingetCreateCore.Models.Installer.NestedInstallerFile>();
cfg.CreateMap<WingetCreateCore.Models.Singleton.Installer, WingetCreateCore.Models.Installer.Installer>();
cfg.CreateMap<WingetCreateCore.Models.Singleton.InstallerSwitches, WingetCreateCore.Models.Installer.InstallerSwitches>();
});
Expand Down