ArchiveFlow.cpp (6886B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "ArchiveFlow.h" 5 #include "PortableFlow.h" 6 #include "ShellExecuteInstallerHandler.h" 7 #include <winget/AdminSettings.h> 8 #include <winget/Archive.h> 9 #include <winget/Filesystem.h> 10 11 using namespace AppInstaller::Manifest; 12 13 namespace AppInstaller::CLI::Workflow 14 { 15 namespace 16 { 17 constexpr std::wstring_view s_Extracted = L"extracted"; 18 } 19 20 void ScanArchiveFromLocalManifest(Execution::Context& context) 21 { 22 if (context.Args.Contains(Execution::Args::Type::Manifest)) 23 { 24 bool scanResult = Archive::ScanZipFile(context.Get<Execution::Data::InstallerPath>()); 25 26 if (scanResult) 27 { 28 AICLI_LOG(CLI, Info, << "Archive malware scan passed"); 29 } 30 else 31 { 32 if (context.Args.Contains(Execution::Args::Type::IgnoreLocalArchiveMalwareScan) && 33 Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride)) 34 { 35 AICLI_LOG(CLI, Warning, << "Archive scan detected malware. Proceeding due to --ignore-local-archive-malware-scan"); 36 context.Reporter.Warn() << Resource::String::ArchiveFailedMalwareScanOverridden << std::endl; 37 } 38 else 39 { 40 AICLI_LOG(CLI, Error, << "Archive malware scan failed"); 41 context.Reporter.Error() << Resource::String::ArchiveFailedMalwareScan << std::endl; 42 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED); 43 } 44 } 45 } 46 } 47 48 void ExtractFilesFromArchive(Execution::Context& context) 49 { 50 const auto& installerPath = context.Get<Execution::Data::InstallerPath>(); 51 std::filesystem::path destinationFolder = installerPath.parent_path() / s_Extracted; 52 std::filesystem::create_directory(destinationFolder); 53 54 AICLI_LOG(CLI, Info, << "Extracting archive to: " << destinationFolder); 55 context.Reporter.Info() << Resource::String::ExtractingArchive << std::endl; 56 57 if (Settings::User().Get<Settings::Setting::ArchiveExtractionMethod>() == Archive::ExtractionMethod::Tar) 58 { 59 context << ShellExecuteExtractArchive(installerPath, destinationFolder); 60 } 61 else 62 { 63 HRESULT result = AppInstaller::Archive::TryExtractArchive(installerPath, destinationFolder); 64 65 if (SUCCEEDED(result)) 66 { 67 AICLI_LOG(CLI, Info, << "Successfully extracted archive"); 68 context.Reporter.Info() << Resource::String::ExtractArchiveSucceeded << std::endl; 69 } 70 else 71 { 72 AICLI_LOG(CLI, Info, << "Failed to extract archive with code " << result); 73 context.Reporter.Error() << Resource::String::ExtractArchiveFailed << std::endl; 74 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); 75 } 76 } 77 } 78 79 void VerifyAndSetNestedInstaller(Execution::Context& context) 80 { 81 const auto& installer = context.Get<Execution::Data::Installer>().value(); 82 if (installer.NestedInstallerFiles.empty()) 83 { 84 // Pre-install validation should prevent this from happening 85 AICLI_LOG(CLI, Error, << "No entries specified for NestedInstallerFiles"); 86 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); 87 } 88 89 std::filesystem::path targetInstallerPath = context.Get<Execution::Data::InstallerPath>().parent_path() / s_Extracted; 90 91 for (const auto& nestedInstallerFile : installer.NestedInstallerFiles) 92 { 93 const std::filesystem::path& nestedInstallerPath = targetInstallerPath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath); 94 95 if (Filesystem::PathEscapesBaseDirectory(nestedInstallerPath, targetInstallerPath)) 96 { 97 AICLI_LOG(CLI, Error, << "Path points to a location outside of the install directory: " << nestedInstallerPath); 98 context.Reporter.Error() << Resource::String::InvalidPathToNestedInstaller << std::endl; 99 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH); 100 } 101 else if (!std::filesystem::exists(nestedInstallerPath)) 102 { 103 AICLI_LOG(CLI, Error, << "Unable to locate nested installer at: " << nestedInstallerPath); 104 context.Reporter.Error() 105 << Resource::String::NestedInstallerNotFound(Utility::LocIndView{ nestedInstallerPath.u8string() }) 106 << std::endl; 107 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND); 108 } 109 else if (!IsPortableType(installer.NestedInstallerType)) 110 { 111 // Update the installerPath to the extracted non-portable installer. 112 AICLI_LOG(CLI, Info, << "Setting installerPath to: " << nestedInstallerPath); 113 targetInstallerPath = nestedInstallerPath; 114 } 115 } 116 117 context.Add<Execution::Data::InstallerPath>(targetInstallerPath); 118 } 119 120 void EnsureValidNestedInstallerMetadataForArchiveInstall(Execution::Context& context) 121 { 122 auto installer = context.Get<Execution::Data::Installer>().value(); 123 124 if (IsArchiveType(installer.BaseInstallerType)) 125 { 126 if (!IsNestedInstallerTypeSupported(installer.NestedInstallerType)) 127 { 128 AICLI_LOG(CLI, Error, << "Nested installer type not supported: " << installer.NestedInstallerType); 129 context.Reporter.Error() << Resource::String::NestedInstallerNotSupported << std::endl; 130 AICLI_TERMINATE_CONTEXT(ERROR_NOT_SUPPORTED); 131 } 132 133 auto const& nestedInstallerFiles = installer.NestedInstallerFiles; 134 if (nestedInstallerFiles.empty()) 135 { 136 AICLI_LOG(CLI, Error, << "No entries specified for NestedInstallerFiles"); 137 context.Reporter.Error() << Resource::String::NestedInstallerNotSpecified << std::endl; 138 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); 139 } 140 141 if (installer.NestedInstallerType != InstallerTypeEnum::Portable && nestedInstallerFiles.size() != 1) 142 { 143 AICLI_LOG(CLI, Error, << "Multiple nested installers specified for non-portable nested installerType"); 144 context.Reporter.Error() << Resource::String::MultipleNonPortableNestedInstallersSpecified << std::endl; 145 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); 146 } 147 } 148 } 149 }