diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 13fe999b38..f063332bb8 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -126,6 +126,8 @@ decompressor dedupe deigh deleteifnotneeded +deliveryoptimization +deliveryoptimizationerrors DENYWR desktopappinstaller devhome @@ -338,6 +340,7 @@ msft msftrubengu MSIHASH MSIXHASH +MSIXSTRM msstore MSZIP mszyml @@ -405,6 +408,7 @@ PACL PARAMETERMAP pathparts Patil +pbstr pcb PCCERT PCs diff --git a/doc/windows/package-manager/winget/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md index ca01e16be7..b37fbe5c6e 100644 --- a/doc/windows/package-manager/winget/returnCodes.md +++ b/doc/windows/package-manager/winget/returnCodes.md @@ -145,6 +145,7 @@ ms.localizationpriority: medium | 0x8A150083 | -1978335101 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED | Failed to retrieve Microsoft Store package license. | | 0x8A150084 | -1978335100 | APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED | The Microsoft Store package does not support download command. | | 0x8A150085 | -1978335099 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN | Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege. | +| 0x8A150086 | -1978335098 | APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE | Downloaded zero byte installer; ensure that your network connection is working properly. | ## Install errors. diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index 53d7c18406..4633b05d33 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once +#include #include #include #include @@ -34,7 +35,7 @@ namespace AppInstaller::CLI::Execution Manifest, PackageVersion, Installer, - HashPair, + DownloadHashInfo, InstallerPath, LogPath, InstallerArgs, @@ -128,9 +129,9 @@ namespace AppInstaller::CLI::Execution }; template <> - struct DataMapping + struct DataMapping { - using value_t = std::pair, std::vector>; + using value_t = std::pair, Utility::DownloadResult>; }; template <> diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 04b932f07f..0e8407ab2e 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -308,6 +308,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); WINGET_DEFINE_RESOURCE_STRINGID(InstallersRequireInstallLocation); WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerZeroByteFile); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled); diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 30a627f4a9..084cbb4d3f 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -172,15 +172,15 @@ namespace AppInstaller::CLI::Workflow // Checks the file hash for an existing installer file. // Returns true if the file exists and its hash matches, false otherwise. // If the hash does not match, deletes the file. - bool ExistingInstallerFileHasHashMatch(const SHA256::HashBuffer& expectedHash, const std::filesystem::path& filePath, SHA256::HashBuffer& fileHash) + bool ExistingInstallerFileHasHashMatch(const SHA256::HashBuffer& expectedHash, const std::filesystem::path& filePath, SHA256::HashDetails& fileHashDetails) { if (std::filesystem::exists(filePath)) { AICLI_LOG(CLI, Info, << "Found existing installer file at '" << filePath << "'. Verifying file hash."); std::ifstream inStream{ filePath, std::ifstream::binary }; - fileHash = SHA256::ComputeHash(inStream); + fileHashDetails = SHA256::ComputeHashDetails(inStream); - if (SHA256::AreEqual(expectedHash, fileHash)) + if (SHA256::AreEqual(expectedHash, fileHashDetails.Hash)) { return true; } @@ -280,11 +280,11 @@ namespace AppInstaller::CLI::Workflow // Try looking for the file with and without extension. auto installerPath = GetInstallerBaseDownloadPath(context); auto installerFilename = GetInstallerPreHashValidationFileName(context); - SHA256::HashBuffer fileHash; - if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHash)) + SHA256::HashDetails fileHashDetails; + if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHashDetails)) { installerFilename = GetInstallerPostHashValidationFileName(context); - if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHash)) + if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHashDetails)) { // No match return; @@ -293,7 +293,8 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << "Existing installer file hash matches. Will use existing installer."); context.Add(installerPath / installerFilename); - context.Add(std::make_pair(installer.Sha256, fileHash)); + context.Add(std::make_pair(installer.Sha256, + DownloadResult{ std::move(fileHashDetails.Hash), fileHashDetails.SizeInBytes })); } void GetInstallerDownloadPath(Execution::Context& context) @@ -325,7 +326,7 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << Resource::String::Downloading << ' ' << Execution::UrlEmphasis << installer.Url << std::endl; - std::optional> hash; + DownloadResult downloadResult; constexpr int MaxRetryCount = 2; constexpr std::chrono::seconds maximumWaitTimeAllowed = 60s; @@ -334,15 +335,22 @@ namespace AppInstaller::CLI::Workflow bool success = false; try { - hash = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download, + downloadResult = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download, installer.Url, installerPath, Utility::DownloadType::Installer, std::placeholders::_1, - true, downloadInfo)); - success = true; + if (downloadResult.SizeInBytes == 0) + { + AICLI_LOG(CLI, Info, << "Got zero byte file; retrying download after a short wait..."); + std::this_thread::sleep_for(5s); + } + else + { + success = true; + } } catch (const ServiceUnavailableException& sue) { @@ -388,13 +396,13 @@ namespace AppInstaller::CLI::Workflow } } - if (!hash) + if (downloadResult.Sha256Hash.empty()) { context.Reporter.Info() << Resource::String::Cancelled << std::endl; AICLI_TERMINATE_CONTEXT(E_ABORT); } - context.Add(std::make_pair(installer.Sha256, hash.value())); + context.Add(std::make_pair(installer.Sha256, downloadResult)); } void GetMsixSignatureHash(Execution::Context& context) @@ -408,9 +416,14 @@ namespace AppInstaller::CLI::Workflow // Signature hash is only used for streaming installs, which don't use proxy Msix::MsixInfo msixInfo(installer.Url); - auto signatureHash = msixInfo.GetSignatureHash(); - context.Add(std::make_pair(installer.SignatureSha256, signatureHash)); + DownloadResult hashInfo{ msixInfo.GetSignatureHash() }; + // Value is ASCII for MSIXSTRM + // A sentinel value to indicate that this is a streaming hash rather than a download. + // The primary purpose is to prevent us from falling into the code path for zero byte files. + hashInfo.SizeInBytes = 0x4D5349585354524D; + + context.Add(std::make_pair(installer.SignatureSha256, hashInfo)); context.Add({ std::make_pair(installer.Url, msixInfo.GetDigest()) }); } catch (...) @@ -427,17 +440,23 @@ namespace AppInstaller::CLI::Workflow void VerifyInstallerHash(Execution::Context& context) { - const auto& hashPair = context.Get(); + const auto& [expectedHash, downloadResult] = context.Get(); if (!std::equal( - hashPair.first.begin(), - hashPair.first.end(), - hashPair.second.begin())) + expectedHash.begin(), + expectedHash.end(), + downloadResult.Sha256Hash.begin())) { bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::HashOverride); const auto& manifest = context.Get(); - Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second, overrideHashMismatch); + Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, expectedHash, downloadResult.Sha256Hash, overrideHashMismatch, downloadResult.SizeInBytes, downloadResult.ContentType); + + if (downloadResult.SizeInBytes == 0) + { + context.Reporter.Error() << Resource::String::InstallerZeroByteFile << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE); + } // If running as admin, do not allow the user to override the hash failure. if (Runtime::IsRunningAsAdmin()) @@ -526,8 +545,9 @@ namespace AppInstaller::CLI::Workflow // Get the hash from the installer file const auto& installerPath = context.Get(); std::ifstream inStream{ installerPath, std::ifstream::binary }; - auto existingFileHash = SHA256::ComputeHash(inStream); - context.Add(std::make_pair(installer.Sha256, existingFileHash)); + auto existingFileHashDetails = SHA256::ComputeHashDetails(inStream); + context.Add(std::make_pair(installer.Sha256, + DownloadResult{ existingFileHashDetails.Hash, existingFileHashDetails.SizeInBytes })); } else if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore) { diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp index 6a8147a086..9b36411334 100644 --- a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp @@ -101,8 +101,8 @@ namespace AppInstaller::CLI::Workflow } // Verify hash - const auto& hashPair = subContext.Get(); - if (std::equal(hashPair.first.begin(), hashPair.first.end(), hashPair.second.begin())) + const auto& hashPair = subContext.Get(); + if (std::equal(hashPair.first.begin(), hashPair.first.end(), hashPair.second.Sha256Hash.begin())) { AICLI_LOG(CLI, Info, << "Microsoft Store package hash verified"); subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageHashVerified << std::endl; diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj index ddaa14aa3b..b39472efdf 100644 --- a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj +++ b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -55,6 +55,7 @@ + diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index 09ceb04d0f..25fcb6d72c 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -270,6 +270,8 @@ public class ErrorCode public const int ERROR_REPAIR_NOT_SUPPORTED = unchecked((int)0x8A15007C); public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D); + public const int ERROR_INSTALLER_ZERO_BYTE_FILE = unchecked((int)0x8A150086); + public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); public const int ERROR_INSTALL_FILE_IN_USE = unchecked((int)0x8A150103); diff --git a/src/AppInstallerCLIE2ETests/DownloadCommand.cs b/src/AppInstallerCLIE2ETests/DownloadCommand.cs index f8e0c0582f..c20426e8d4 100644 --- a/src/AppInstallerCLIE2ETests/DownloadCommand.cs +++ b/src/AppInstallerCLIE2ETests/DownloadCommand.cs @@ -6,8 +6,8 @@ namespace AppInstallerCLIE2ETests { - using System.IO; - using AppInstallerCLIE2ETests.Helpers; + using System.IO; + using AppInstallerCLIE2ETests.Helpers; using Microsoft.Management.Deployment; using NUnit.Framework; using Windows.System; @@ -27,8 +27,8 @@ public void DownloadDependencies() var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.PackageDependency --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); var dependenciesDir = Path.Combine(downloadDir, Constants.Dependencies); - Assert.True(TestCommon.VerifyInstallerDownload(dependenciesDir, "TestPortableExe", "3.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Portable, "en-US")); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US")); + TestCommon.AssertInstallerDownload(dependenciesDir, "TestPortableExe", "3.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Portable, "en-US"); + TestCommon.AssertInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US"); } /// @@ -42,7 +42,7 @@ public void DownloadDependencies_Skip() Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Dependencies skipped.")); Assert.IsFalse(Directory.Exists(Path.Combine(downloadDir, Constants.Dependencies))); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US"); } /// @@ -55,7 +55,7 @@ public void DownloadToDefaultDirectory() var result = TestCommon.RunAICLICommand("download", $"{Constants.ExeInstallerPackageId} --version {packageVersion}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ExeInstallerPackageId}_{packageVersion}"); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe)); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); } /// @@ -67,7 +67,7 @@ public void DownloadToDirectory() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"{Constants.ExeInstallerPackageId} --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe)); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); } /// @@ -79,7 +79,7 @@ public void DownloadWithUserScope() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestMultipleInstallers --scope user --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.User, PackageInstallerType.Nullsoft, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.User, PackageInstallerType.Nullsoft, "en-US"); } /// @@ -91,7 +91,7 @@ public void DownloadWithMachineScope() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestMultipleInstallers --scope machine --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US"); } /// @@ -103,7 +103,7 @@ public void DownloadWithZipInstallerTypeArg() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestMultipleInstallers --installer-type zip --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true)); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true); } /// @@ -115,7 +115,7 @@ public void DownloadWithInstallerTypeArg() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestMultipleInstallers --installer-type msi --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US"); } /// @@ -127,7 +127,7 @@ public void DownloadWithArchitectureArg() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestMultipleInstallers --architecture x86 --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US"); } /// @@ -139,7 +139,7 @@ public void DownloadWithLocaleArg() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestMultipleInstallers --locale zh-CN --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true)); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true); } /// @@ -152,5 +152,28 @@ public void DownloadWithHashMismatch() var errorResult = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestExeSha256Mismatch --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, errorResult.ExitCode); } + + /// + /// Downloads the zero byte test installer with a hash mismatch. + /// + [Test] + public void DownloadZeroByteFileWithHashMismatch() + { + var downloadDir = TestCommon.GetRandomTestDir(); + var errorResult = TestCommon.RunAICLICommand("download", $"ZeroByteFile.IncorrectHash --download-directory {downloadDir}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_ZERO_BYTE_FILE, errorResult.ExitCode); + } + + /// + /// Downloads the zero byte test installer. + /// + [Test] + public void DownloadZeroByteFile() + { + var downloadDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("download", $"ZeroByteFile.CorrectHash --download-directory {downloadDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + TestCommon.AssertInstallerDownload(downloadDir, "ZeroByteFile-CorrectHash", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US"); + } } -} \ No newline at end of file +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 678102ee77..88fb61d5c0 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -499,7 +499,7 @@ public static bool VerifyTestExeRepairSuccessful(string installDir, string expec } /// - /// Verify installer and manifest downloaded correctly and cleanup. + /// Assert installer and manifest downloaded correctly and cleanup. /// /// Download directory. /// Package name. @@ -510,8 +510,7 @@ public static bool VerifyTestExeRepairSuccessful(string installDir, string expec /// Installer locale. /// Boolean value indicating whether the installer is an archive. /// Boolean value indicating whether to remove the installer file and directory. - /// True if success. - public static bool VerifyInstallerDownload( + public static void AssertInstallerDownload( string downloadDir, string name, string version, @@ -554,21 +553,14 @@ public static bool VerifyInstallerDownload( string installerDownloadPath = Path.Combine(downloadDir, expectedFileName + installerExtension); string manifestDownloadPath = Path.Combine(downloadDir, expectedFileName + ".yaml"); - bool downloadResult = false; + Assert.IsTrue(Directory.Exists(downloadDir), $"Download directory does not exist: {downloadDir}"); + Assert.IsTrue(File.Exists(installerDownloadPath), $"Installer file does not exist: {installerDownloadPath}"); + Assert.IsTrue(File.Exists(manifestDownloadPath), $"Manifest file does not exist: {manifestDownloadPath}"); - if (Directory.Exists(downloadDir) && File.Exists(installerDownloadPath) && File.Exists(manifestDownloadPath)) + if (cleanup) { - downloadResult = true; - - if (cleanup) - { - File.Delete(installerDownloadPath); - File.Delete(manifestDownloadPath); - Directory.Delete(downloadDir, true); - } + Directory.Delete(downloadDir, true); } - - return downloadResult; } /// diff --git a/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs b/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs index 240074c58f..31cc247aa5 100644 --- a/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -8,8 +8,8 @@ namespace AppInstallerCLIE2ETests.Interop { using System; using System.IO; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; using NUnit.Framework; @@ -66,8 +66,8 @@ public async Task DownloadDependencies() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); var dependenciesDir = Path.Combine(downloadDir, Constants.Dependencies); - Assert.True(TestCommon.VerifyInstallerDownload(dependenciesDir, "TestPortableExe", "3.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Portable, "en-US")); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US")); + TestCommon.AssertInstallerDownload(dependenciesDir, "TestPortableExe", "3.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Portable, "en-US"); + TestCommon.AssertInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US"); } /// @@ -94,7 +94,7 @@ public async Task DownloadDependencies_Skip() Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); var dependenciesDir = Path.Combine(downloadDir, Constants.Dependencies); Assert.IsFalse(Directory.Exists(dependenciesDir)); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestPackageDependency", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "en-US"); } /// @@ -118,7 +118,7 @@ public async Task DownloadToDefaultDirectory() var packageVersion = "2.0.0.0"; Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ExeInstallerPackageId}_{packageVersion}"); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe)); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); } /// @@ -142,7 +142,7 @@ public async Task DownloadToDirectory() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe)); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); } /// @@ -167,7 +167,7 @@ public async Task DownloadWithUserScope() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.User, PackageInstallerType.Nullsoft, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.User, PackageInstallerType.Nullsoft, "en-US"); } /// @@ -192,7 +192,7 @@ public async Task DownloadWithMachineScope() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US"); } /// @@ -217,7 +217,7 @@ public async Task DownloadWithZipInstallerTypeArg() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true)); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true); } /// @@ -242,7 +242,7 @@ public async Task DownloadWithInstallerTypeArg() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US"); } /// @@ -267,7 +267,7 @@ public async Task DownloadWithArchitectureArg() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Machine, PackageInstallerType.Msi, "en-US"); } /// @@ -292,7 +292,7 @@ public async Task DownloadWithLocaleArg() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true)); + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.X64, TestCommon.Scope.Unknown, PackageInstallerType.Exe, "zh-CN", true); } /// diff --git a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs index 6b2e5ad826..45f34a91d3 100644 --- a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs @@ -158,7 +158,7 @@ public async Task DisableWinGetCommandLineInterfacesPolicy() Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); var packageVersion = "2.0.0.0"; string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ModifyRepairInstaller}_{packageVersion}"); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestModifyRepair", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Burn, "en-US")); + TestCommon.AssertInstallerDownload(downloadDir, "TestModifyRepair", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Burn, "en-US"); } } } diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_CorrectHash.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_CorrectHash.yaml new file mode 100644 index 0000000000..c658a51477 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_CorrectHash.yaml @@ -0,0 +1,14 @@ +PackageIdentifier: ZeroByteFile.CorrectHash +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: ZeroByteFile-CorrectHash +ShortDescription: Points to a zero byte installer file using the correct hash +Publisher: Microsoft Corporation +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/TestData/empty + InstallerType: exe + InstallerSha256: E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_IncorrectHash.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_IncorrectHash.yaml new file mode 100644 index 0000000000..f076dd4a10 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_IncorrectHash.yaml @@ -0,0 +1,14 @@ +PackageIdentifier: ZeroByteFile.IncorrectHash +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: ZeroByteFile-IncorrectHash +ShortDescription: Points to a zero byte installer file using the incorrect hash +Publisher: Microsoft Corporation +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/TestData/empty + InstallerType: exe + InstallerSha256: BAD0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852BBAD +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/empty b/src/AppInstallerCLIE2ETests/TestData/empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index fc42b2ddfa..9f0f88b8df 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3133,4 +3133,10 @@ Please specify one of them using the --source option to proceed. Parameter cannot be passed across integrity boundary. + + Downloaded zero byte installer; ensure that your network connection is working properly. + + + Downloaded zero byte installer; ensure that your network connection is working properly. + \ No newline at end of file diff --git a/src/AppInstallerCLITests/Downloader.cpp b/src/AppInstallerCLITests/Downloader.cpp index 95f8337818..530eb41a08 100644 --- a/src/AppInstallerCLITests/Downloader.cpp +++ b/src/AppInstallerCLITests/Downloader.cpp @@ -17,10 +17,10 @@ TEST_CASE("DownloadValidFileAndVerifyHash", "[Downloader]") // Todo: point to files from our repo when the repo goes public ProgressCallback callback; - auto result = Download("https://raw.githubusercontent.com/microsoft/msix-packaging/master/LICENSE", tempFile.GetPath(), DownloadType::Manifest, callback, true); + auto result = Download("https://raw.githubusercontent.com/microsoft/msix-packaging/master/LICENSE", tempFile.GetPath(), DownloadType::Manifest, callback); - REQUIRE(result.has_value()); - auto resultHash = result.value(); + REQUIRE(!result.Sha256Hash.empty()); + auto resultHash = result.Sha256Hash; auto expectedHash = SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); REQUIRE(std::equal( @@ -28,7 +28,12 @@ TEST_CASE("DownloadValidFileAndVerifyHash", "[Downloader]") expectedHash.end(), resultHash.begin())); - REQUIRE(std::filesystem::file_size(tempFile.GetPath()) > 0); + uint64_t expectedFileSize = 1119; + REQUIRE(result.SizeInBytes == expectedFileSize); + REQUIRE(std::filesystem::file_size(tempFile.GetPath()) == expectedFileSize); + + REQUIRE(result.ContentType); + REQUIRE(!result.ContentType.value().empty()); // Verify motw content std::filesystem::path motwFile(tempFile); @@ -47,17 +52,17 @@ TEST_CASE("DownloadValidFileAndCancel", "[Downloader]") ProgressCallback callback; - std::optional> waitResult; + DownloadResult waitResult; std::thread waitThread([&] { - waitResult = Download("https://aka.ms/win32-x64-user-stable", tempFile.GetPath(), DownloadType::Installer, callback, true); + waitResult = Download("https://aka.ms/win32-x64-user-stable", tempFile.GetPath(), DownloadType::Installer, callback); }); callback.Cancel(); waitThread.join(); - REQUIRE(!waitResult.has_value()); + REQUIRE(waitResult.Sha256Hash.empty()); } TEST_CASE("DownloadInvalidUrl", "[Downloader]") @@ -67,7 +72,7 @@ TEST_CASE("DownloadInvalidUrl", "[Downloader]") ProgressCallback callback; - REQUIRE_THROWS(Download("blargle-flargle-fluff", tempFile.GetPath(), DownloadType::Installer, callback, true)); + REQUIRE_THROWS(Download("blargle-flargle-fluff", tempFile.GetPath(), DownloadType::Installer, callback)); } TEST_CASE("HttpStream_ReadLastFullPage", "[HttpStream]") diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index 5cf6229805..f65408959d 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -34,7 +34,7 @@ void OverrideForDirectMsi(TestContext& context) context.Override({ DownloadInstallerFile, [](TestContext& context) { - context.Add({ {}, {} }); + context.Add({ {}, {} }); // We don't have an msi installer for tests, but we won't execute it anyway context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); } }); @@ -92,7 +92,7 @@ TEST_CASE("InstallFlow_RenameFromEncodedUrl", "[InstallFlow][workflow]") OverrideForCheckExistingInstaller(context); context.Override({ DownloadInstallerFile, [](TestContext& context) { - context.Add({ {}, {} }); + context.Add({ {}, {} }); auto installerPath = std::filesystem::temp_directory_path(); installerPath /= "EncodedUrlTest.exe"; std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); @@ -124,7 +124,7 @@ TEST_CASE("InstallFlow_RenameFromInvalidFileCharacterUrl", "[InstallFlow][workfl OverrideForCheckExistingInstaller(context); context.Override({ DownloadInstallerFile, [](TestContext& context) { - context.Add({ {}, {} }); + context.Add({ {}, {} }); auto installerPath = std::filesystem::temp_directory_path(); installerPath /= "InvalidFileCharacterUrlTest.exe"; std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index de7701264d..ce69a4d12d 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -45,7 +45,7 @@ TEST_CASE("VerifyInstallerTrustLevelAndUpdateInstallerFileMotw", "[DownloadInsta std::ostringstream updateMotwOutput; TestContext context{ updateMotwOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); - context.Add({ {}, {} }); + context.Add({ {}, {} }); context.Add(testInstallerPath); auto packageVersion = std::make_shared(Manifest{}); auto testSource = std::make_shared(); diff --git a/src/AppInstallerCLITests/WorkflowCommon.cpp b/src/AppInstallerCLITests/WorkflowCommon.cpp index c704bd55dd..51d004c583 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.cpp +++ b/src/AppInstallerCLITests/WorkflowCommon.cpp @@ -558,7 +558,7 @@ namespace TestCommon context.Override({ DownloadInstallerFile, [](TestContext& context) { - context.Add({ {}, {} }); + context.Add({ {}, {} }); context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); }, expectedUseCount }); @@ -573,7 +573,7 @@ namespace TestCommon { context.Override({ DownloadInstallerFile, [&installationLog](TestContext& context) { - context.Add({ {}, {} }); + context.Add({ {}, {} }); context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); auto dependency = Dependency(DependencyType::Package, context.Get().Id, context.Get().Version); @@ -602,7 +602,7 @@ namespace TestCommon { context.Override({ DownloadInstallerFile, [](TestContext& context) { - context.Add({ {}, {} }); + context.Add({ {}, {} }); context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); } }); @@ -626,7 +626,7 @@ namespace TestCommon std::ifstream inStream{ tempInstallerPath, std::ifstream::binary }; SHA256::HashBuffer fileHash = SHA256::ComputeHash(inStream); - context.Add({ fileHash, fileHash }); + context.Add({ fileHash, DownloadResult{ fileHash } }); } }); context.Override({ RenameDownloadedInstaller, [](TestContext&) @@ -732,7 +732,7 @@ namespace TestCommon std::ofstream file(installerPath, std::ofstream::out | std::ofstream::trunc); file << installer.Url; file.close(); - context.Add({ {}, {} }); + context.Add({ {}, {} }); } }); } } diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp index 9fb07722b2..448e33d52b 100644 --- a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp +++ b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -546,8 +546,12 @@ namespace AppInstaller::Logging std::string_view channel, const std::vector& expected, const std::vector& actual, - bool overrideHashMismatch) const noexcept + bool overrideHashMismatch, + uint64_t downloadSizeInBytes, + const std::optional& contentType) const noexcept { + std::string actualContentType = contentType.value_or(std::string{}); + if (IsTelemetryEnabled()) { AICLI_TraceLoggingWriteActivity( @@ -559,6 +563,8 @@ namespace AppInstaller::Logging TraceLoggingBinary(expected.data(), static_cast(expected.size()), "Expected"), TraceLoggingBinary(actual.data(), static_cast(actual.size()), "Actual"), TraceLoggingBool(overrideHashMismatch, "Override"), + TraceLoggingUInt64(downloadSizeInBytes, "ActualSize"), + AICLI_TraceLoggingStringView(actualContentType, "ContentType"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); @@ -570,6 +576,8 @@ namespace AppInstaller::Logging m_summary.HashMismatchExpected = expected; m_summary.HashMismatchActual = actual; m_summary.HashMismatchOverride = overrideHashMismatch; + m_summary.HashMismatchActualSize = downloadSizeInBytes; + m_summary.HashMismatchContentType = actualContentType; } } @@ -578,7 +586,7 @@ namespace AppInstaller::Logging << Utility::SHA256::ConvertToString(expected) << "] does not match download [" << Utility::SHA256::ConvertToString(actual) - << ']'); + << "] with file size [" << downloadSizeInBytes << "] and content type [" << actualContentType << "]"); } void TelemetryTraceLogger::LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept @@ -812,6 +820,8 @@ namespace AppInstaller::Logging TraceLoggingBinary(m_summary.HashMismatchExpected.data(), static_cast(m_summary.HashMismatchExpected.size()), "HashMismatchExpected"), TraceLoggingBinary(m_summary.HashMismatchActual.data(), static_cast(m_summary.HashMismatchActual.size()), "HashMismatchActual"), TraceLoggingBool(m_summary.HashMismatchOverride, "HashMismatchOverride"), + TraceLoggingUInt64(m_summary.HashMismatchActualSize, "HashMismatchActualSize"), + AICLI_TraceLoggingStringView(m_summary.HashMismatchContentType, "HashMismatchContentType"), AICLI_TraceLoggingStringView(m_summary.InstallerExecutionType, "InstallerExecutionType"), TraceLoggingUInt32(m_summary.InstallerErrorCode, "InstallerErrorCode"), AICLI_TraceLoggingStringView(m_summary.UninstallerExecutionType, "UninstallerExecutionType"), diff --git a/src/AppInstallerCommonCore/DODownloader.cpp b/src/AppInstallerCommonCore/DODownloader.cpp index 4398ce7ccb..988a0f754e 100644 --- a/src/AppInstallerCommonCore/DODownloader.cpp +++ b/src/AppInstallerCommonCore/DODownloader.cpp @@ -8,20 +8,44 @@ #include "winget/UserSettings.h" // TODO: Get this from the Windows SDK when available -#include "external/do.h" +#define DODownloadProperty_HttpRedirectionTarget static_cast(DODownloadProperty_NonVolatile + 1) +#define DODownloadProperty_HttpResponseHeaders static_cast(DODownloadProperty_HttpRedirectionTarget + 1) +#define DODownloadProperty_HttpServerIPAddress static_cast(DODownloadProperty_HttpResponseHeaders + 1) +#define DODownloadProperty_HttpStatusCode static_cast(DODownloadProperty_HttpServerIPAddress + 1) namespace AppInstaller::Utility { - namespace DeliveryOptimization + namespace { -// TODO: Once the SDK headers are available, remove these defines -#define DO_E_DOWNLOAD_NO_PROGRESS HRESULT(0x80D02002L) // Download of a file saw no progress within the defined period + std::optional ExtractContentType(const std::optional& headers) + { + if (!headers) + { + return std::nullopt; + } + + static constexpr std::string_view s_ContentType = "content-type:"sv; + auto headerLines = Utility::SplitIntoLines(headers.value()); + + for (const auto& header : headerLines) + { + std::string_view headerView = header; + if (header.length() >= s_ContentType.length()) + { + std::string lowerFragment = ToLower(headerView.substr(0, s_ContentType.length())); + if (s_ContentType == lowerFragment) + { + return Trim(header.substr(s_ContentType.length())); + } + } + } -#define DO_E_BLOCKED_BY_COST_TRANSFER_POLICY HRESULT(0x80D03801L) // DO core paused the job due to cost policy restrictions -#define DO_E_BLOCKED_BY_CELLULAR_POLICY HRESULT(0x80D03803L) // DO core paused the job due to detection of cellular network and policy restrictions -#define DO_E_BLOCKED_BY_POWER_STATE HRESULT(0x80D03804L) // DO core paused the job due to detection of power state change into non-AC mode -#define DO_E_BLOCKED_BY_NO_NETWORK HRESULT(0x80D03805L) // DO core paused the job due to loss of network connectivity + return std::nullopt; + } + } + namespace DeliveryOptimization + { // Represents a download work item for Delivery Optimization. struct Download { @@ -106,6 +130,23 @@ namespace AppInstaller::Utility THROW_IF_FAILED(m_download->SetProperty(prop, &var)); } + template + std::optional TryGetProperty(DODownloadProperty prop) + { + std::optional result; + wil::unique_variant var; + HRESULT hr = m_download->GetProperty(prop, &var); + if (SUCCEEDED(hr)) + { + T value; + if (ExtractFromVariant(var, value)) + { + result = std::move(value); + } + } + return result; + } + void Uri(std::string_view uri) { SetProperty(DODownloadProperty_Uri, uri); @@ -186,6 +227,22 @@ namespace AppInstaller::Utility } private: + bool ExtractFromVariant(const VARIANT& var, std::string& value) + { + if (var.vt == VT_BSTR && var.bstrVal != nullptr) + { + value = Utility::ConvertToUTF8(var.bstrVal); + return true; + } + else if (var.vt == (VT_BSTR | VT_BYREF) && var.pbstrVal != nullptr && *var.pbstrVal != nullptr) + { + value = Utility::ConvertToUTF8(*var.pbstrVal); + return true; + } + + return false; + } + wil::com_ptr m_download; }; @@ -221,7 +278,7 @@ namespace AppInstaller::Utility { } - IFACEMETHOD(OnStatusChange)(IDODownload*, DO_DOWNLOAD_STATUS* status) + IFACEMETHOD(OnStatusChange)(IDODownload*, const DO_DOWNLOAD_STATUS* status) { { std::lock_guard guard(m_statusMutex); @@ -341,11 +398,10 @@ namespace AppInstaller::Utility // Debugging tip: // From an elevated PowerShell, run: // > Get-DeliveryOptimizationLog | Set-Content doLogs.txt - std::optional> DODownload( + DownloadResult DODownload( const std::string& url, const std::filesystem::path& dest, IProgressCallback& progress, - bool computeHash, std::optional info) { AICLI_LOG(Core, Info, << "DeliveryOptimization downloading from url: " << url); @@ -397,15 +453,22 @@ namespace AppInstaller::Utility // Wait returns true for success, false for cancellation, and throws on error. if (callback->Wait()) { + // Grab the headers so that we can use them later + std::optional responseHeaders = download.TryGetProperty(DODownloadProperty_HttpResponseHeaders); + // Finalize is required to flush the data and change the file name. download.Finalize(); AICLI_LOG(Core, Info, << "Download completed."); - if (computeHash) - { - std::ifstream inStream{ dest, std::ifstream::binary }; - return SHA256::ComputeHash(inStream); - } + std::ifstream inStream{ dest, std::ifstream::binary }; + auto hashDetails = SHA256::ComputeHashDetails(inStream); + + DownloadResult result; + result.Sha256Hash = std::move(hashDetails.Hash); + result.SizeInBytes = hashDetails.SizeInBytes; + result.ContentType = ExtractContentType(responseHeaders); + + return result; } return {}; diff --git a/src/AppInstallerCommonCore/DODownloader.h b/src/AppInstallerCommonCore/DODownloader.h index 1950daeb46..a5186a4e55 100644 --- a/src/AppInstallerCommonCore/DODownloader.h +++ b/src/AppInstallerCommonCore/DODownloader.h @@ -15,11 +15,10 @@ namespace AppInstaller::Utility // url: The url to be downloaded from. http->https redirection is allowed. // dest: The stream to be downloaded to. // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. - std::optional> DODownload( + DownloadResult DODownload( const std::string& url, const std::filesystem::path& dest, IProgressCallback& progress, - bool computeHash, std::optional info); // Returns true if the error from DODownload should be treated as fatal; diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp index aa956f6de4..f96e0c0176 100644 --- a/src/AppInstallerCommonCore/Downloader.cpp +++ b/src/AppInstallerCommonCore/Downloader.cpp @@ -27,6 +27,47 @@ namespace AppInstaller::Utility { namespace { + std::wstring GetHttpQueryString(const wil::unique_hinternet& urlFile, DWORD queryProperty) + { + std::wstring result = {}; + DWORD length = 0; + if (!HttpQueryInfoW(urlFile.get(), + queryProperty, + &result[0], + &length, + nullptr)) + { + auto lastError = GetLastError(); + if (lastError == ERROR_INSUFFICIENT_BUFFER) + { + // lpdwBufferLength contains the size, in bytes, of a buffer large enough to receive the requested information + // without the nul char. not the exact buffer size. + auto size = static_cast(length) / sizeof(wchar_t); + result.resize(size + 1); + if (HttpQueryInfoW(urlFile.get(), + queryProperty, + &result[0], + &length, + nullptr)) + { + // because the buffer can be bigger remove possible null chars + result.erase(result.find(L'\0')); + } + else + { + AICLI_LOG(Core, Error, << "Error retrieving header value [" << queryProperty << "]: " << GetLastError()); + result.clear(); + } + } + else + { + AICLI_LOG(Core, Error, << "Error retrieving header [" << queryProperty << "]: " << GetLastError()); + } + } + + return result; + } + // Gets the retry after value in terms of a delay in seconds std::chrono::seconds GetRetryAfter(const HttpDateOrDeltaHeaderValue& retryAfter) { @@ -56,47 +97,15 @@ namespace AppInstaller::Utility std::chrono::seconds GetRetryAfter(const wil::unique_hinternet& urlFile) { - std::wstring retryAfter = {}; - DWORD length = 0; - if (!HttpQueryInfoW(urlFile.get(), - HTTP_QUERY_RETRY_AFTER, - &retryAfter, - &length, - nullptr)) - { - auto lastError = GetLastError(); - if (lastError == ERROR_INSUFFICIENT_BUFFER) - { - // lpdwBufferLength contains the size, in bytes, of a buffer large enough to receive the requested information - // without the nul char. not the exact buffer size. - auto size = static_cast(length) / sizeof(wchar_t); - retryAfter.resize(size + 1); - if (HttpQueryInfoW(urlFile.get(), - HTTP_QUERY_RETRY_AFTER, - &retryAfter[0], - &length, - nullptr)) - { - // because the buffer can be bigger remove possible null chars - retryAfter.erase(retryAfter.find(L'\0')); - return AppInstaller::Utility::GetRetryAfter(retryAfter); - } - } - else - { - AICLI_LOG(Core, Error, << "Error retrieving Retry-After header: " << GetLastError()); - } - } - - return 0s; + std::wstring retryAfter = GetHttpQueryString(urlFile, HTTP_QUERY_RETRY_AFTER); + return retryAfter.empty() ? 0s : AppInstaller::Utility::GetRetryAfter(retryAfter); } } - std::optional> WinINetDownloadToStream( + DownloadResult WinINetDownloadToStream( const std::string& url, std::ostream& dest, - IProgressCallback& progress, - bool computeHash) + IProgressCallback& progress) { // For AICLI_LOG usages with string literals. #pragma warning(push) @@ -181,6 +190,9 @@ namespace AppInstaller::Utility nullptr); AICLI_LOG(Core, Verbose, << "Download size: " << contentLength); + std::string contentType = Utility::ConvertToUTF8(GetHttpQueryString(urlFile, HTTP_QUERY_CONTENT_TYPE)); + AICLI_LOG(Core, Verbose, << "Content Type: " << contentType); + // Setup hash engine SHA256 hashEngine; @@ -203,10 +215,7 @@ namespace AppInstaller::Utility THROW_LAST_ERROR_IF_MSG(!readSuccess, "InternetReadFile() failed."); - if (computeHash) - { - hashEngine.Add(buffer.get(), bytesRead); - } + hashEngine.Add(buffer.get(), bytesRead); dest.write((char*)buffer.get(), bytesRead); @@ -227,12 +236,11 @@ namespace AppInstaller::Utility THROW_HR_IF(APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH, bytesDownloaded != contentLength); } - std::vector result; - if (computeHash) - { - result = hashEngine.Get(); - AICLI_LOG(Core, Info, << "Download hash: " << SHA256::ConvertToString(result)); - } + DownloadResult result; + result.SizeInBytes = static_cast(bytesDownloaded); + result.ContentType = std::move(contentType); + result.Sha256Hash = hashEngine.Get(); + AICLI_LOG(Core, Info, << "Download hash: " << SHA256::ConvertToString(result.Sha256Hash)); AICLI_LOG(Core, Info, << "Download completed."); @@ -283,24 +291,22 @@ namespace AppInstaller::Utility return result; } - std::optional> DownloadToStream( + DownloadResult DownloadToStream( const std::string& url, std::ostream& dest, DownloadType, IProgressCallback& progress, - bool computeHash, std::optional) { THROW_HR_IF(E_INVALIDARG, url.empty()); - return WinINetDownloadToStream(url, dest, progress, computeHash); + return WinINetDownloadToStream(url, dest, progress); } - std::optional> Download( + DownloadResult Download( const std::string& url, const std::filesystem::path& dest, DownloadType type, IProgressCallback& progress, - bool computeHash, std::optional info) { THROW_HR_IF(E_INVALIDARG, url.empty()); @@ -320,7 +326,7 @@ namespace AppInstaller::Utility { try { - auto result = DODownload(url, dest, progress, computeHash, info); + auto result = DODownload(url, dest, progress, info); // Since we cannot pre-apply to the file with DO, post-apply the MotW to the file. // Only do so if the file exists, because cancellation will not throw here. if (std::filesystem::exists(dest)) @@ -362,7 +368,7 @@ namespace AppInstaller::Utility // Use std::ofstream::app to append to previous empty file so that it will not // create a new file and clear motw. std::ofstream outfile(dest, std::ofstream::binary | std::ofstream::app); - return WinINetDownloadToStream(url, outfile, progress, computeHash); + return WinINetDownloadToStream(url, outfile, progress); } using namespace std::string_view_literals; diff --git a/src/AppInstallerCommonCore/FileCache.cpp b/src/AppInstallerCommonCore/FileCache.cpp index 23a1ef98ea..331c44ed16 100644 --- a/src/AppInstallerCommonCore/FileCache.cpp +++ b/src/AppInstallerCommonCore/FileCache.cpp @@ -51,12 +51,12 @@ namespace AppInstaller::Caching { try { - auto downloadHash = Utility::DownloadToStream(fullPath, *result, Utility::DownloadType::Manifest, emptyCallback, !expectedHash.empty()); + auto downloadResult = Utility::DownloadToStream(fullPath, *result, Utility::DownloadType::Manifest, emptyCallback); if (!expectedHash.empty() && - (!downloadHash || !Utility::SHA256::AreEqual(expectedHash, downloadHash.value()))) + !Utility::SHA256::AreEqual(expectedHash, downloadResult.Sha256Hash)) { - AICLI_LOG(Core, Verbose, << "Invalid hash from [" << fullPath << "]: expected [" << Utility::SHA256::ConvertToString(expectedHash) << "], got [" << (downloadHash ? Utility::SHA256::ConvertToString(*downloadHash) : "null") << "]"); + AICLI_LOG(Core, Verbose, << "Invalid hash from [" << fullPath << "]: expected [" << Utility::SHA256::ConvertToString(expectedHash) << "], got [" << Utility::SHA256::ConvertToString(downloadResult.Sha256Hash) << "]"); THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h index 1850a44be6..1c71b148dd 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h @@ -36,6 +36,14 @@ namespace AppInstaller::Utility std::string ContentId; }; + // Properties about the downloaded file. + struct DownloadResult + { + std::vector Sha256Hash; + uint64_t SizeInBytes = 0; + std::optional ContentType; + }; + // An exception that indicates that a remote service is too busy/unavailable and may contain data on when to try again. struct ServiceUnavailableException : public wil::ResultException { @@ -52,12 +60,11 @@ namespace AppInstaller::Utility // dest: The stream to be downloaded to. // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. // downloadInfo: Optional. Currently only used by DO to identify the download. - std::optional> DownloadToStream( + DownloadResult DownloadToStream( const std::string& url, std::ostream& dest, DownloadType type, IProgressCallback& progress, - bool computeHash = false, std::optional downloadInfo = {}); // Downloads a file from the given URL and places it in the given location. @@ -65,12 +72,11 @@ namespace AppInstaller::Utility // dest: The path to local file to be downloaded to. // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. // downloadInfo: Optional. Currently only used by DO to identify the download. - std::optional> Download( + DownloadResult Download( const std::string& url, const std::filesystem::path& dest, DownloadType type, IProgressCallback& progress, - bool computeHash = false, std::optional downloadInfo = {}); // Gets the headers for the given URL. diff --git a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h index 3a39fb343d..9d7452e0ff 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h @@ -101,6 +101,8 @@ namespace AppInstaller::Logging std::vector HashMismatchExpected; std::vector HashMismatchActual; bool HashMismatchOverride = false; + uint64_t HashMismatchActualSize = 0; + std::string HashMismatchContentType; // LogInstallerFailure std::string InstallerExecutionType; @@ -232,7 +234,9 @@ namespace AppInstaller::Logging std::string_view channel, const std::vector& expected, const std::vector& actual, - bool overrideHashMismatch) const noexcept; + bool overrideHashMismatch, + uint64_t downloadSizeInBytes, + const std::optional& contentType) const noexcept; // Logs a failed installation attempt. void LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept; diff --git a/src/AppInstallerCommonCore/external/README.md b/src/AppInstallerCommonCore/external/README.md deleted file mode 100644 index 55589d4fe6..0000000000 --- a/src/AppInstallerCommonCore/external/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This is a temporary location to store headers from external sources: -1. do.h - - This header for the DeliveryOptimization COM API is not included in the Windows SDK currently, but should be in the near future. While we wait for this fix, we will use this file. \ No newline at end of file diff --git a/src/AppInstallerCommonCore/external/do.h b/src/AppInstallerCommonCore/external/do.h deleted file mode 100644 index e689b1ba41..0000000000 --- a/src/AppInstallerCommonCore/external/do.h +++ /dev/null @@ -1,566 +0,0 @@ - - -/* this ALWAYS GENERATED file contains the definitions for the interfaces */ - - - /* File created by MIDL compiler version 8.01.0626 */ -/* @@MIDL_FILE_HEADING( ) */ - - - -/* verify that the version is high enough to compile this file*/ -#ifndef __REQUIRED_RPCNDR_H_VERSION__ -#define __REQUIRED_RPCNDR_H_VERSION__ 500 -#endif - -/* verify that the version is high enough to compile this file*/ -#ifndef __REQUIRED_RPCSAL_H_VERSION__ -#define __REQUIRED_RPCSAL_H_VERSION__ 100 -#endif - -#include "rpc.h" -#include "rpcndr.h" - -#ifndef __RPCNDR_H_VERSION__ -#error this stub requires an updated version of -#endif /* __RPCNDR_H_VERSION__ */ - -#ifndef COM_NO_WINDOWS_H -#include "windows.h" -#include "ole2.h" -#endif /*COM_NO_WINDOWS_H*/ - -#ifndef __do_h__ -#define __do_h__ - -#if defined(_MSC_VER) && (_MSC_VER >= 1020) -#pragma once -#endif - -#ifndef DECLSPEC_XFGVIRT -#if _CONTROL_FLOW_GUARD_XFG -#define DECLSPEC_XFGVIRT(base, func) __declspec(xfg_virtual(base, func)) -#else -#define DECLSPEC_XFGVIRT(base, func) -#endif -#endif - -/* Forward Declarations */ - -#ifndef __IDODownload_FWD_DEFINED__ -#define __IDODownload_FWD_DEFINED__ -typedef interface IDODownload IDODownload; - -#endif /* __IDODownload_FWD_DEFINED__ */ - - -#ifndef __IDODownloadStatusCallback_FWD_DEFINED__ -#define __IDODownloadStatusCallback_FWD_DEFINED__ -typedef interface IDODownloadStatusCallback IDODownloadStatusCallback; - -#endif /* __IDODownloadStatusCallback_FWD_DEFINED__ */ - - -#ifndef __IDOManager_FWD_DEFINED__ -#define __IDOManager_FWD_DEFINED__ -typedef interface IDOManager IDOManager; - -#endif /* __IDOManager_FWD_DEFINED__ */ - - -#ifndef __DeliveryOptimization_FWD_DEFINED__ -#define __DeliveryOptimization_FWD_DEFINED__ - -#ifdef __cplusplus -typedef class DeliveryOptimization DeliveryOptimization; -#else -typedef struct DeliveryOptimization DeliveryOptimization; -#endif /* __cplusplus */ - -#endif /* __DeliveryOptimization_FWD_DEFINED__ */ - - -/* header files for imported files */ -#include "oaidl.h" - -#ifdef __cplusplus -extern "C"{ -#endif - - -/* interface __MIDL_itf_do_0000_0000 */ -/* [local] */ - -typedef struct _DO_DOWNLOAD_RANGE - { - UINT64 Offset; - UINT64 Length; - } DO_DOWNLOAD_RANGE; - -typedef struct _DO_DOWNLOAD_RANGES_INFO - { - UINT RangeCount; - /* [size_is] */ DO_DOWNLOAD_RANGE Ranges[ 1 ]; - } DO_DOWNLOAD_RANGES_INFO; - -typedef -enum _DODownloadState - { - DODownloadState_Created = 0, - DODownloadState_Transferring = ( DODownloadState_Created + 1 ) , - DODownloadState_Transferred = ( DODownloadState_Transferring + 1 ) , - DODownloadState_Finalized = ( DODownloadState_Transferred + 1 ) , - DODownloadState_Aborted = ( DODownloadState_Finalized + 1 ) , - DODownloadState_Paused = ( DODownloadState_Aborted + 1 ) - } DODownloadState; - -typedef struct _DO_DOWNLOAD_STATUS - { - UINT64 BytesTotal; - UINT64 BytesTransferred; - DODownloadState State; - HRESULT Error; - HRESULT ExtendedError; - } DO_DOWNLOAD_STATUS; - -typedef -enum _DODownloadCostPolicy - { - DODownloadCostPolicy_Always = 0, - DODownloadCostPolicy_Unrestricted = ( DODownloadCostPolicy_Always + 1 ) , - DODownloadCostPolicy_Standard = ( DODownloadCostPolicy_Unrestricted + 1 ) , - DODownloadCostPolicy_NoRoaming = ( DODownloadCostPolicy_Standard + 1 ) , - DODownloadCostPolicy_NoSurcharge = ( DODownloadCostPolicy_NoRoaming + 1 ) , - DODownloadCostPolicy_NoCellular = ( DODownloadCostPolicy_NoSurcharge + 1 ) - } DODownloadCostPolicy; - -typedef -enum _DODownloadProperty - { - DODownloadProperty_Id = 0, - DODownloadProperty_Uri = ( DODownloadProperty_Id + 1 ) , - DODownloadProperty_ContentId = ( DODownloadProperty_Uri + 1 ) , - DODownloadProperty_DisplayName = ( DODownloadProperty_ContentId + 1 ) , - DODownloadProperty_LocalPath = ( DODownloadProperty_DisplayName + 1 ) , - DODownloadProperty_HttpCustomHeaders = ( DODownloadProperty_LocalPath + 1 ) , - DODownloadProperty_CostPolicy = ( DODownloadProperty_HttpCustomHeaders + 1 ) , - DODownloadProperty_SecurityFlags = ( DODownloadProperty_CostPolicy + 1 ) , - DODownloadProperty_CallbackFreqPercent = ( DODownloadProperty_SecurityFlags + 1 ) , - DODownloadProperty_CallbackFreqSeconds = ( DODownloadProperty_CallbackFreqPercent + 1 ) , - DODownloadProperty_NoProgressTimeoutSeconds = ( DODownloadProperty_CallbackFreqSeconds + 1 ) , - DODownloadProperty_ForegroundPriority = ( DODownloadProperty_NoProgressTimeoutSeconds + 1 ) , - DODownloadProperty_BlockingMode = ( DODownloadProperty_ForegroundPriority + 1 ) , - DODownloadProperty_CallbackInterface = ( DODownloadProperty_BlockingMode + 1 ) , - DODownloadProperty_StreamInterface = ( DODownloadProperty_CallbackInterface + 1 ) , - DODownloadProperty_SecurityContext = ( DODownloadProperty_StreamInterface + 1 ) , - DODownloadProperty_NetworkToken = ( DODownloadProperty_SecurityContext + 1 ) , - DODownloadProperty_CorrelationVector = ( DODownloadProperty_NetworkToken + 1 ) , - DODownloadProperty_DecryptionInfo = ( DODownloadProperty_CorrelationVector + 1 ) , - DODownloadProperty_IntegrityCheckInfo = ( DODownloadProperty_DecryptionInfo + 1 ) , - DODownloadProperty_IntegrityCheckMandatory = ( DODownloadProperty_IntegrityCheckInfo + 1 ) , - DODownloadProperty_TotalSizeBytes = ( DODownloadProperty_IntegrityCheckMandatory + 1 ) , - DODownloadProperty_DisallowOnCellular = ( DODownloadProperty_TotalSizeBytes + 1 ) , - DODownloadProperty_HttpCustomAuthHeaders = ( DODownloadProperty_DisallowOnCellular + 1 ) - } DODownloadProperty; - -typedef struct _DO_DOWNLOAD_ENUM_CATEGORY - { - DODownloadProperty Property; - LPCWSTR Value; - } DO_DOWNLOAD_ENUM_CATEGORY; - - - -extern RPC_IF_HANDLE __MIDL_itf_do_0000_0000_v0_0_c_ifspec; -extern RPC_IF_HANDLE __MIDL_itf_do_0000_0000_v0_0_s_ifspec; - -#ifndef __IDODownload_INTERFACE_DEFINED__ -#define __IDODownload_INTERFACE_DEFINED__ - -/* interface IDODownload */ -/* [uuid][object] */ - - -EXTERN_C const IID IID_IDODownload; - -#if defined(__cplusplus) && !defined(CINTERFACE) - - MIDL_INTERFACE("FBBD7FC0-C147-4727-A38D-827EF071EE77") - IDODownload : public IUnknown - { - public: - virtual HRESULT STDMETHODCALLTYPE Start( - /* [unique][in] */ __RPC__in_opt DO_DOWNLOAD_RANGES_INFO *ranges) = 0; - - virtual HRESULT STDMETHODCALLTYPE Pause( void) = 0; - - virtual HRESULT STDMETHODCALLTYPE Abort( void) = 0; - - virtual HRESULT STDMETHODCALLTYPE Finalize( void) = 0; - - virtual HRESULT STDMETHODCALLTYPE GetStatus( - /* [out] */ __RPC__out DO_DOWNLOAD_STATUS *status) = 0; - - virtual HRESULT STDMETHODCALLTYPE GetProperty( - /* [in] */ DODownloadProperty propId, - /* [out] */ __RPC__out VARIANT *propVal) = 0; - - virtual HRESULT STDMETHODCALLTYPE SetProperty( - /* [in] */ DODownloadProperty propId, - /* [in] */ __RPC__in VARIANT *propVal) = 0; - - }; - - -#else /* C style interface */ - - typedef struct IDODownloadVtbl - { - BEGIN_INTERFACE - - DECLSPEC_XFGVIRT(IUnknown, QueryInterface) - HRESULT ( STDMETHODCALLTYPE *QueryInterface )( - __RPC__in IDODownload * This, - /* [in] */ __RPC__in REFIID riid, - /* [annotation][iid_is][out] */ - _COM_Outptr_ void **ppvObject); - - DECLSPEC_XFGVIRT(IUnknown, AddRef) - ULONG ( STDMETHODCALLTYPE *AddRef )( - __RPC__in IDODownload * This); - - DECLSPEC_XFGVIRT(IUnknown, Release) - ULONG ( STDMETHODCALLTYPE *Release )( - __RPC__in IDODownload * This); - - DECLSPEC_XFGVIRT(IDODownload, Start) - HRESULT ( STDMETHODCALLTYPE *Start )( - __RPC__in IDODownload * This, - /* [unique][in] */ __RPC__in_opt DO_DOWNLOAD_RANGES_INFO *ranges); - - DECLSPEC_XFGVIRT(IDODownload, Pause) - HRESULT ( STDMETHODCALLTYPE *Pause )( - __RPC__in IDODownload * This); - - DECLSPEC_XFGVIRT(IDODownload, Abort) - HRESULT ( STDMETHODCALLTYPE *Abort )( - __RPC__in IDODownload * This); - - DECLSPEC_XFGVIRT(IDODownload, Finalize) - HRESULT ( STDMETHODCALLTYPE *Finalize )( - __RPC__in IDODownload * This); - - DECLSPEC_XFGVIRT(IDODownload, GetStatus) - HRESULT ( STDMETHODCALLTYPE *GetStatus )( - __RPC__in IDODownload * This, - /* [out] */ __RPC__out DO_DOWNLOAD_STATUS *status); - - DECLSPEC_XFGVIRT(IDODownload, GetProperty) - HRESULT ( STDMETHODCALLTYPE *GetProperty )( - __RPC__in IDODownload * This, - /* [in] */ DODownloadProperty propId, - /* [out] */ __RPC__out VARIANT *propVal); - - DECLSPEC_XFGVIRT(IDODownload, SetProperty) - HRESULT ( STDMETHODCALLTYPE *SetProperty )( - __RPC__in IDODownload * This, - /* [in] */ DODownloadProperty propId, - /* [in] */ __RPC__in VARIANT *propVal); - - END_INTERFACE - } IDODownloadVtbl; - - interface IDODownload - { - CONST_VTBL struct IDODownloadVtbl *lpVtbl; - }; - - - -#ifdef COBJMACROS - - -#define IDODownload_QueryInterface(This,riid,ppvObject) \ - ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) - -#define IDODownload_AddRef(This) \ - ( (This)->lpVtbl -> AddRef(This) ) - -#define IDODownload_Release(This) \ - ( (This)->lpVtbl -> Release(This) ) - - -#define IDODownload_Start(This,ranges) \ - ( (This)->lpVtbl -> Start(This,ranges) ) - -#define IDODownload_Pause(This) \ - ( (This)->lpVtbl -> Pause(This) ) - -#define IDODownload_Abort(This) \ - ( (This)->lpVtbl -> Abort(This) ) - -#define IDODownload_Finalize(This) \ - ( (This)->lpVtbl -> Finalize(This) ) - -#define IDODownload_GetStatus(This,status) \ - ( (This)->lpVtbl -> GetStatus(This,status) ) - -#define IDODownload_GetProperty(This,propId,propVal) \ - ( (This)->lpVtbl -> GetProperty(This,propId,propVal) ) - -#define IDODownload_SetProperty(This,propId,propVal) \ - ( (This)->lpVtbl -> SetProperty(This,propId,propVal) ) - -#endif /* COBJMACROS */ - - -#endif /* C style interface */ - - - - -#endif /* __IDODownload_INTERFACE_DEFINED__ */ - - -#ifndef __IDODownloadStatusCallback_INTERFACE_DEFINED__ -#define __IDODownloadStatusCallback_INTERFACE_DEFINED__ - -/* interface IDODownloadStatusCallback */ -/* [uuid][object] */ - - -EXTERN_C const IID IID_IDODownloadStatusCallback; - -#if defined(__cplusplus) && !defined(CINTERFACE) - - MIDL_INTERFACE("D166E8E3-A90E-4392-8E87-05E996D3747D") - IDODownloadStatusCallback : public IUnknown - { - public: - virtual HRESULT STDMETHODCALLTYPE OnStatusChange( - /* [in] */ __RPC__in_opt IDODownload *download, - /* [in] */ __RPC__in DO_DOWNLOAD_STATUS *status) = 0; - - }; - - -#else /* C style interface */ - - typedef struct IDODownloadStatusCallbackVtbl - { - BEGIN_INTERFACE - - DECLSPEC_XFGVIRT(IUnknown, QueryInterface) - HRESULT ( STDMETHODCALLTYPE *QueryInterface )( - __RPC__in IDODownloadStatusCallback * This, - /* [in] */ __RPC__in REFIID riid, - /* [annotation][iid_is][out] */ - _COM_Outptr_ void **ppvObject); - - DECLSPEC_XFGVIRT(IUnknown, AddRef) - ULONG ( STDMETHODCALLTYPE *AddRef )( - __RPC__in IDODownloadStatusCallback * This); - - DECLSPEC_XFGVIRT(IUnknown, Release) - ULONG ( STDMETHODCALLTYPE *Release )( - __RPC__in IDODownloadStatusCallback * This); - - DECLSPEC_XFGVIRT(IDODownloadStatusCallback, OnStatusChange) - HRESULT ( STDMETHODCALLTYPE *OnStatusChange )( - __RPC__in IDODownloadStatusCallback * This, - /* [in] */ __RPC__in_opt IDODownload *download, - /* [in] */ __RPC__in DO_DOWNLOAD_STATUS *status); - - END_INTERFACE - } IDODownloadStatusCallbackVtbl; - - interface IDODownloadStatusCallback - { - CONST_VTBL struct IDODownloadStatusCallbackVtbl *lpVtbl; - }; - - - -#ifdef COBJMACROS - - -#define IDODownloadStatusCallback_QueryInterface(This,riid,ppvObject) \ - ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) - -#define IDODownloadStatusCallback_AddRef(This) \ - ( (This)->lpVtbl -> AddRef(This) ) - -#define IDODownloadStatusCallback_Release(This) \ - ( (This)->lpVtbl -> Release(This) ) - - -#define IDODownloadStatusCallback_OnStatusChange(This,download,status) \ - ( (This)->lpVtbl -> OnStatusChange(This,download,status) ) - -#endif /* COBJMACROS */ - - -#endif /* C style interface */ - - - - -#endif /* __IDODownloadStatusCallback_INTERFACE_DEFINED__ */ - - -#ifndef __IDOManager_INTERFACE_DEFINED__ -#define __IDOManager_INTERFACE_DEFINED__ - -/* interface IDOManager */ -/* [uuid][object] */ - - -EXTERN_C const IID IID_IDOManager; - -#if defined(__cplusplus) && !defined(CINTERFACE) - - MIDL_INTERFACE("400E2D4A-1431-4C1A-A748-39CA472CFDB1") - IDOManager : public IUnknown - { - public: - virtual HRESULT STDMETHODCALLTYPE CreateDownload( - /* [out] */ __RPC__deref_out_opt IDODownload **download) = 0; - - virtual HRESULT STDMETHODCALLTYPE EnumDownloads( - /* [unique][in] */ __RPC__in_opt DO_DOWNLOAD_ENUM_CATEGORY *category, - /* [out] */ __RPC__deref_out_opt IEnumUnknown **ppEnum) = 0; - - }; - - -#else /* C style interface */ - - typedef struct IDOManagerVtbl - { - BEGIN_INTERFACE - - DECLSPEC_XFGVIRT(IUnknown, QueryInterface) - HRESULT ( STDMETHODCALLTYPE *QueryInterface )( - __RPC__in IDOManager * This, - /* [in] */ __RPC__in REFIID riid, - /* [annotation][iid_is][out] */ - _COM_Outptr_ void **ppvObject); - - DECLSPEC_XFGVIRT(IUnknown, AddRef) - ULONG ( STDMETHODCALLTYPE *AddRef )( - __RPC__in IDOManager * This); - - DECLSPEC_XFGVIRT(IUnknown, Release) - ULONG ( STDMETHODCALLTYPE *Release )( - __RPC__in IDOManager * This); - - DECLSPEC_XFGVIRT(IDOManager, CreateDownload) - HRESULT ( STDMETHODCALLTYPE *CreateDownload )( - __RPC__in IDOManager * This, - /* [out] */ __RPC__deref_out_opt IDODownload **download); - - DECLSPEC_XFGVIRT(IDOManager, EnumDownloads) - HRESULT ( STDMETHODCALLTYPE *EnumDownloads )( - __RPC__in IDOManager * This, - /* [unique][in] */ __RPC__in_opt DO_DOWNLOAD_ENUM_CATEGORY *category, - /* [out] */ __RPC__deref_out_opt IEnumUnknown **ppEnum); - - END_INTERFACE - } IDOManagerVtbl; - - interface IDOManager - { - CONST_VTBL struct IDOManagerVtbl *lpVtbl; - }; - - - -#ifdef COBJMACROS - - -#define IDOManager_QueryInterface(This,riid,ppvObject) \ - ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) - -#define IDOManager_AddRef(This) \ - ( (This)->lpVtbl -> AddRef(This) ) - -#define IDOManager_Release(This) \ - ( (This)->lpVtbl -> Release(This) ) - - -#define IDOManager_CreateDownload(This,download) \ - ( (This)->lpVtbl -> CreateDownload(This,download) ) - -#define IDOManager_EnumDownloads(This,category,ppEnum) \ - ( (This)->lpVtbl -> EnumDownloads(This,category,ppEnum) ) - -#endif /* COBJMACROS */ - - -#endif /* C style interface */ - - - - -#endif /* __IDOManager_INTERFACE_DEFINED__ */ - - - -#ifndef __DeliveryOptimization_LIBRARY_DEFINED__ -#define __DeliveryOptimization_LIBRARY_DEFINED__ - -/* library DeliveryOptimization */ -/* [uuid] */ - - -EXTERN_C const IID LIBID_DeliveryOptimization; - -EXTERN_C const CLSID CLSID_DeliveryOptimization; - -#ifdef __cplusplus - -class DECLSPEC_UUID("5b99fa76-721c-423c-adac-56d03c8a8007") -DeliveryOptimization; -#endif -#endif /* __DeliveryOptimization_LIBRARY_DEFINED__ */ - -/* interface __MIDL_itf_do_0000_0004 */ -/* [local] */ - -#define DO_LENGTH_TO_EOF (UINT64)(-1) - -#define DecryptionInfo_KeyData L"KeyData" -#define DecryptionInfo_EncryptionBufferSize L"EncryptionBufferSize" -#define DecryptionInfo_AlgorithmName L"AlgorithmName" -#define DecryptionInfo_ChainingMode L"ChainingMode" - -#define IntegrityCheckInfo_PiecesHashFileUrl L"PiecesHashFileUrl" -#define IntegrityCheckInfo_PiecesHashFileDigest L"PiecesHashFileDigest" -#define IntegrityCheckInfo_PiecesHashFileDigestAlgorithm L"PiecesHashFileDigestAlgorithm" -#define IntegrityCheckInfo_HashOfHashes L"HashOfHashes" - - -extern RPC_IF_HANDLE __MIDL_itf_do_0000_0004_v0_0_c_ifspec; -extern RPC_IF_HANDLE __MIDL_itf_do_0000_0004_v0_0_s_ifspec; - -/* Additional Prototypes for ALL interfaces */ - -unsigned long __RPC_USER VARIANT_UserSize( __RPC__in unsigned long *, unsigned long , __RPC__in VARIANT * ); -unsigned char * __RPC_USER VARIANT_UserMarshal( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in VARIANT * ); -unsigned char * __RPC_USER VARIANT_UserUnmarshal(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out VARIANT * ); -void __RPC_USER VARIANT_UserFree( __RPC__in unsigned long *, __RPC__in VARIANT * ); - -unsigned long __RPC_USER VARIANT_UserSize64( __RPC__in unsigned long *, unsigned long , __RPC__in VARIANT * ); -unsigned char * __RPC_USER VARIANT_UserMarshal64( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in VARIANT * ); -unsigned char * __RPC_USER VARIANT_UserUnmarshal64(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out VARIANT * ); -void __RPC_USER VARIANT_UserFree64( __RPC__in unsigned long *, __RPC__in VARIANT * ); - -/* end of Additional Prototypes */ - -#ifdef __cplusplus -} -#endif - -#endif - - diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h index 4b1d61e814..2825b5ff5c 100644 --- a/src/AppInstallerCommonCore/pch.h +++ b/src/AppInstallerCommonCore/pch.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include "TraceLogging.h" diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp index 2ecd66e8e3..87f63ab9ac 100644 --- a/src/AppInstallerSharedLib/Errors.cpp +++ b/src/AppInstallerSharedLib/Errors.cpp @@ -221,6 +221,7 @@ namespace AppInstaller WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED, "Failed to retrieve Microsoft Store package license."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED, "The Microsoft Store package does not support download."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN, "Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have the required privilege."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE, "Downloaded zero byte installer; ensure that your network connection is working properly."), // Install errors. WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, "Application is currently running. Exit the application then try again."), diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index ad5680d24c..d00bc8a18a 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -151,6 +151,7 @@ #define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED ((HRESULT)0x8A150083) #define APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED ((HRESULT)0x8A150084) #define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN ((HRESULT)0x8A150085) +#define APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE ((HRESULT)0x8A150086) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h b/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h index 0416c47dc4..bdd5b483d3 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h @@ -23,6 +23,12 @@ namespace AppInstaller::Utility { constexpr static size_t HashBufferSizeInBytes = 32; constexpr static size_t HashStringSizeInChars = 64; + struct HashDetails + { + HashBuffer Hash; + uint64_t SizeInBytes = 0; + }; + SHA256(); // Adds the next chunk of data to the hash. @@ -56,6 +62,9 @@ namespace AppInstaller::Utility { // Computes the hash from a given stream. static HashBuffer ComputeHash(std::istream& in); + // Computes the hash from a given stream. + static HashDetails ComputeHashDetails(std::istream& in); + // Computes the hash from a given file path. static HashBuffer ComputeHashFromFile(const std::filesystem::path& path); diff --git a/src/AppInstallerSharedLib/SHA256.cpp b/src/AppInstallerSharedLib/SHA256.cpp index dfaaf0daa3..8843a6d2d2 100644 --- a/src/AppInstallerSharedLib/SHA256.cpp +++ b/src/AppInstallerSharedLib/SHA256.cpp @@ -121,6 +121,11 @@ namespace AppInstaller::Utility { } SHA256::HashBuffer SHA256::ComputeHash(std::istream& in) + { + return ComputeHashDetails(in).Hash; + } + + SHA256::HashDetails SHA256::ComputeHashDetails(std::istream& in) { // Throw exceptions on badbit auto excState = in.exceptions(); @@ -131,19 +136,25 @@ namespace AppInstaller::Utility { auto buffer = std::make_unique(bufferSize); SHA256 hasher; + uint64_t totalSize = 0; while (in.good()) { in.read((char*)(buffer.get()), bufferSize); - if (in.gcount()) + std::streamsize bytesRead = in.gcount(); + if (bytesRead) { - hasher.Add(buffer.get(), static_cast(in.gcount())); + hasher.Add(buffer.get(), static_cast(bytesRead)); + totalSize += static_cast(bytesRead); } } if (in.eof()) { - return hasher.Get(); + HashDetails result; + result.Hash = hasher.Get(); + result.SizeInBytes = totalSize; + return result; } else { @@ -151,7 +162,6 @@ namespace AppInstaller::Utility { } } - SHA256::HashBuffer SHA256::ComputeHashFromFile(const std::filesystem::path& path) { std::ifstream inStream{ path, std::ifstream::binary }; diff --git a/src/LocalhostWebServer/Startup.cs b/src/LocalhostWebServer/Startup.cs index a0348c485c..de71561b2b 100644 --- a/src/LocalhostWebServer/Startup.cs +++ b/src/LocalhostWebServer/Startup.cs @@ -63,13 +63,14 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) provider.Mappings[".appxbundle"] = "application/vns.ms-appx"; provider.Mappings[".mszyml"] = "application/x-ms-zip-yaml"; - //Enable static file serving app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(StaticFileRoot), RequestPath = StaticFileRequestPath, ContentTypeProvider = provider, + ServeUnknownFileTypes = true, + DefaultContentType = "application/octet-stream" }); app.UseDirectoryBrowser(new DirectoryBrowserOptions diff --git a/src/WinGetUtil/Exports.cpp b/src/WinGetUtil/Exports.cpp index 4ed8dc9b9b..bef1c56b59 100644 --- a/src/WinGetUtil/Exports.cpp +++ b/src/WinGetUtil/Exports.cpp @@ -529,12 +529,12 @@ extern "C" THROW_HR_IF(E_INVALIDARG, computeHash && sha256HashLength != 32); AppInstaller::ProgressCallback callback; - auto hashValue = Download(ConvertToUTF8(url), filePath, DownloadType::WinGetUtil, callback, computeHash); + auto downloadResult = Download(ConvertToUTF8(url), filePath, DownloadType::WinGetUtil, callback); // At this point, if computeHash is set we have verified that the buffer is valid and 32 bytes. if (computeHash) { - const auto& hash = hashValue.value(); + const auto& hash = downloadResult.Sha256Hash; // The SHA 256 hash length should always be 32 bytes. THROW_HR_IF(E_UNEXPECTED, hash.size() != sha256HashLength);