From 881904c9116bfee91f665cb737f556678eeaf25a Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 1 Aug 2023 18:37:25 -0700 Subject: [PATCH] Implement DownloadCommandProhibited (#3487) --- .../package-manager/winget/returnCodes.md | 2 + .../v1.6.0/manifest.installer.1.6.0.json | 10 + .../v1.6.0/manifest.singleton.1.6.0.json | 10 + src/AppInstallerCLICore/Resources.h | 1 + .../Workflows/DownloadFlow.cpp | 19 +- .../Workflows/DownloadFlow.h | 6 + .../Workflows/InstallFlow.cpp | 4 +- src/AppInstallerCLIE2ETests/Constants.cs | 4 +- .../Shared/Strings/en-us/winget.resw | 3 + .../AppInstallerCLITests.vcxproj | 5 + .../AppInstallerCLITests.vcxproj.filters | 9 + src/AppInstallerCLITests/DownloadFlow.cpp | 24 ++ src/AppInstallerCLITests/RestClient.cpp | 2 +- .../RestInterface_1_6.cpp | 405 ++++++++++++++++++ ...oadFlowTest_DownloadCommandProhibited.yaml | 15 + .../TestData/ManifestV1_6-Singleton.yaml | 2 + .../ManifestV1_6-MultiFile-Installer.yaml | 2 + src/AppInstallerCLITests/YamlManifest.cpp | 15 + .../Manifest/ManifestCommon.cpp | 2 +- .../Manifest/ManifestSchemaValidation.cpp | 1 + .../Manifest/ManifestYamlPopulator.cpp | 10 + .../Manifest/YamlWriter.cpp | 2 + .../Public/winget/ManifestInstaller.h | 2 + .../AppInstallerRepositoryCore.vcxproj | 4 + ...AppInstallerRepositoryCore.vcxproj.filters | 18 + .../ManifestJSONParser.cpp | 7 +- .../Rest/RestClient.cpp | 7 +- .../Rest/Schema/1_6/Interface.h | 21 + .../Schema/1_6/Json/ManifestDeserializer.h | 17 + .../1_6/Json/ManifestDeserializer_1_6.cpp | 35 ++ .../Rest/Schema/1_6/RestInterface_1_6.cpp | 25 ++ .../Rest/Schema/CommonRestConstants.h | 1 + src/AppInstallerSharedLib/Errors.cpp | 2 + .../Public/AppInstallerErrors.h | 1 + 34 files changed, 686 insertions(+), 7 deletions(-) create mode 100644 src/AppInstallerCLITests/DownloadFlow.cpp create mode 100644 src/AppInstallerCLITests/RestInterface_1_6.cpp create mode 100644 src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp diff --git a/doc/windows/package-manager/winget/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md index 6c3c525ad0..d7b6eaceba 100644 --- a/doc/windows/package-manager/winget/returnCodes.md +++ b/doc/windows/package-manager/winget/returnCodes.md @@ -119,6 +119,8 @@ ms.localizationpriority: medium | 0x8A150069 | -1978335127 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB | The package currently installed is the stub package | | 0x8A15006A | -1978335126 | APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED | Application shutdown signal received | | 0x8A15006B | -1978335125 | APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES | Failed to download package dependencies. | +| 0x8A15006C | -1978335124 | APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED | Failed to download package. Download for offline installation is prohibited. | + ## Install errors. diff --git a/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json b/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json index e149711ac2..2f2fa51333 100644 --- a/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json +++ b/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json @@ -576,6 +576,10 @@ }, "description": "Details about the installation. Used for deeper installation detection." }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, "Installer": { "type": "object", "properties": { @@ -690,6 +694,9 @@ }, "InstallationMetadata": { "$ref": "#/$defs/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/$defs/DownloadCommandProhibited" } }, "required": [ @@ -803,6 +810,9 @@ "InstallationMetadata": { "$ref": "#/$defs/InstallationMetadata" }, + "DownloadCommandProhibited": { + "$ref": "#/$defs/DownloadCommandProhibited" + }, "Installers": { "type": "array", "items": { diff --git a/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json b/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json index e9595d3df7..44934d6fc4 100644 --- a/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json +++ b/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json @@ -675,6 +675,10 @@ }, "description": "Details about the installation. Used for deeper installation detection." }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, "Installer": { "type": "object", "properties": { @@ -789,6 +793,9 @@ }, "InstallationMetadata": { "$ref": "#/$defs/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/$defs/DownloadCommandProhibited" } }, "required": [ @@ -1025,6 +1032,9 @@ "InstallationMetadata": { "$ref": "#/$defs/InstallationMetadata" }, + "DownloadCommandProhibited": { + "$ref": "#/$defs/DownloadCommandProhibited" + }, "Installers": { "type": "array", "items": { diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 71a3dc793c..17d1303977 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -228,6 +228,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstalledScopeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstallerAbortsTerminal); WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadCommandProhibited); WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloaded); WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloads); WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected); diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 891ccd7fe4..e77e7a1560 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -591,4 +591,21 @@ namespace AppInstaller::CLI::Workflow YamlWriter::OutputYamlFile(manifest, installer.value(), manifestDownloadPath); AICLI_LOG(CLI, Info, << "Successfully generated manifest yaml. Path: " << manifestDownloadPath); } -} \ No newline at end of file + + void EnsureSupportForDownload(Execution::Context& context) + { + // No checks needed if not download installer only. + if (WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + { + return; + } + + const auto& installer = context.Get(); + + if (installer->DownloadCommandProhibited) + { + context.Reporter.Error() << Resource::String::InstallerDownloadCommandProhibited << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED); + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.h b/src/AppInstallerCLICore/Workflows/DownloadFlow.h index 581b728b06..2ff6e15210 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.h +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.h @@ -78,4 +78,10 @@ namespace AppInstaller::CLI::Workflow // Inputs: Manifest, Installer, DownloadDirectory // Outputs: None void ExportManifest(Execution::Context& context); + + // This method ensures requirements of download for later offline installation. + // Required Args: None + // Inputs: Installer + // Outputs: None + void EnsureSupportForDownload(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 7fee223db2..8c6046059a 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -293,7 +293,9 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); } - context << EnsureSupportForInstall; + context << + EnsureSupportForDownload << + EnsureSupportForInstall; } void CheckForUnsupportedArgs(Execution::Context& context) diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index 65e66dae9a..a3f41110dd 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -245,7 +245,9 @@ public class ErrorCode public const int ERROR_NOT_ALL_QUERIES_FOUND_SINGLE = unchecked((int)0x8A150067); public const int ERROR_PACKAGE_IS_PINNED = unchecked((int)0x8A150068); public const int ERROR_PACKAGE_IS_STUB = unchecked((int)0x8A150069); - public const int ERROR_DOWNLOAD_DEPENDENCIES = unchecked((int)0x8A15006A); + public const int ERROR_APPTERMINATION_RECEIVED = unchecked((int)0x8A15006A); + public const int ERROR_DOWNLOAD_DEPENDENCIES = unchecked((int)0x8A15006B); + public const int ERROR_DOWNLOAD_COMMAND_PROHIBITED = unchecked((int)0x8A15006C); public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 30c4a7db20..e108ca1a0b 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -2056,4 +2056,7 @@ Please specify one of them using the --source option to proceed. Installer Downloads + + Installer is prohibited from being downloaded for later offline installation. + \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 82e6059dcc..f797368b33 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -205,6 +205,7 @@ + @@ -243,6 +244,7 @@ + @@ -283,6 +285,9 @@ true + + true + true diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 6618fcdd05..b63318bfed 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -308,6 +308,12 @@ Source Files\CLI + + Source Files\Repository + + + Source Files\CLI + @@ -573,6 +579,9 @@ TestData + + TestData + TestData diff --git a/src/AppInstallerCLITests/DownloadFlow.cpp b/src/AppInstallerCLITests/DownloadFlow.cpp new file mode 100644 index 0000000000..3e76830a33 --- /dev/null +++ b/src/AppInstallerCLITests/DownloadFlow.cpp @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; + +TEST_CASE("DownloadFlow_DownloadCommandProhibited", "[DownloadFlow][workflow]") +{ + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_DownloadCommandProhibited.yaml").GetPath().u8string()); + + DownloadCommand download({}); + download.Execute(context); + INFO(downloadOutput.str()); + + // Verify AppInfo is printed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED); + REQUIRE(downloadOutput.str().find(Resource::LocString(Resource::String::InstallerDownloadCommandProhibited).get()) != std::string::npos); +} diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp index 8438c95413..9a7d443d17 100644 --- a/src/AppInstallerCLITests/RestClient.cpp +++ b/src/AppInstallerCLITests/RestClient.cpp @@ -50,7 +50,7 @@ TEST_CASE("GetSupportedInterface", "[RestSource]") REQUIRE(RestClient::GetSupportedInterface(utility::conversions::to_utf8string(TestRestUri), {}, info, version)->GetVersion() == version); // Update this test to next version so that we don't forget to add to supported versions before rest e2e tests are available. - Version invalid{ "1.6.0" }; + Version invalid{ "1.7.0" }; REQUIRE_THROWS(RestClient::GetSupportedInterface(utility::conversions::to_utf8string(TestRestUri), {}, info, invalid)); } diff --git a/src/AppInstallerCLITests/RestInterface_1_6.cpp b/src/AppInstallerCLITests/RestInterface_1_6.cpp new file mode 100644 index 0000000000..cf43b663a7 --- /dev/null +++ b/src/AppInstallerCLITests/RestInterface_1_6.cpp @@ -0,0 +1,405 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_6; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + IRestClient::Information GetTestSourceInformation() + { + IRestClient::Information result; + + result.RequiredPackageMatchFields.emplace_back("Market"); + result.RequiredQueryParameters.emplace_back("Market"); + result.UnsupportedPackageMatchFields.emplace_back("Moniker"); + result.UnsupportedQueryParameters.emplace_back("Channel"); + + return result; + } + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "install", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + }, + "DownloadCommandProhibited": true + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get() == "Installation Notes"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 7); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + REQUIRE(actualInstaller.DownloadCommandProhibited); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_6", "[RestSource][Interface_1_6]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_6{ TestRestUriString, {}, {}, std::move(helper) }; + std::vector manifests = v1_6.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.6.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml b/src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml new file mode 100644 index 0000000000..7cc23ec810 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml @@ -0,0 +1,15 @@ +PackageIdentifier: AppInstallerCliTest.DownloadCommandProhibited +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test DownloadCommandProhibited +Publisher: Microsoft Corporation +AppMoniker: AICLITestDownloadCommandProhibited +License: Test +DownloadCommandProhibited: true +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml index f7246a7ef8..b3dc6efbd9 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml @@ -121,6 +121,7 @@ InstallationMetadata: FileType: launch InvocationParameter: "/arg" DisplayName: "DisplayName" +DownloadCommandProhibited: true Installers: - Architecture: x86 @@ -189,5 +190,6 @@ Installers: - InstallerReturnCode: 3 ReturnResponse: custom ReturnResponseUrl: https://defaultReturnResponseUrl.com + DownloadCommandProhibited: false ManifestType: singleton ManifestVersion: 1.6.0 \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_6/ManifestV1_6-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_6/ManifestV1_6-MultiFile-Installer.yaml index 09fb1ee837..85d292556b 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_6/ManifestV1_6-MultiFile-Installer.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_6/ManifestV1_6-MultiFile-Installer.yaml @@ -86,6 +86,7 @@ InstallationMetadata: FileType: launch InvocationParameter: "/arg" DisplayName: "DisplayName" +DownloadCommandProhibited: true Installers: - Architecture: x86 @@ -154,6 +155,7 @@ Installers: ReturnResponseUrl: https://defaultReturnResponseUrl.com UnsupportedArguments: - location + DownloadCommandProhibited: false - Architecture: x64 InstallerType: exe InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 0b881540a9..ee50f420fe 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -522,6 +522,11 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).InvocationParameter == "/arg"); } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) + { + REQUIRE(manifest.DefaultInstallerInfo.DownloadCommandProhibited); + } } if (isSingleton || isExported) @@ -621,6 +626,11 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(installer1.InstallationMetadata.Files.at(0).DisplayName == "DisplayName"); } + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) + { + REQUIRE_FALSE(installer1.DownloadCommandProhibited); + } + if (!isSingleton) { if (!isExported) @@ -692,6 +702,11 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(installer4.InstallationMetadata.Files.at(0).InvocationParameter == "/arg2"); REQUIRE(installer4.InstallationMetadata.Files.at(0).DisplayName == "DisplayName2"); } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) + { + REQUIRE(installer2.DownloadCommandProhibited); + } } // Localization diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index e45a9da217..1e27aac395 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -1085,7 +1085,7 @@ namespace AppInstaller::Manifest { if (!minVersion.empty()) { - return dependency.MinVersion.value() == minVersion; + return dependency.MinVersion.has_value() && dependency.MinVersion.value() == Utility::Version{ minVersion }; } else { diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index cd5372e6cd..16a31de326 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -32,6 +32,7 @@ namespace AppInstaller::Manifest::YamlParser { "RequireExplicitUpgrade"sv, YamlScalarType::Bool }, { "DisplayInstallWarnings"sv, YamlScalarType::Bool }, { "InstallerReturnCode"sv, YamlScalarType::Int }, + { "DownloadCommandProhibited", YamlScalarType::Bool } }; YamlScalarType GetManifestScalarValueType(const std::string& key) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 082da5a9e4..c3dad85c2f 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -319,6 +319,16 @@ namespace AppInstaller::Manifest std::move(fields_v1_4.begin(), fields_v1_4.end(), std::inserter(result, result.end())); } + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_6 }) + { + std::vector fields_v1_6 = + { + { "DownloadCommandProhibited", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->DownloadCommandProhibited = value.as(); return {}; }, true }, + }; + + std::move(fields_v1_6.begin(), fields_v1_6.end(), std::inserter(result, result.end())); + } } return result; diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp index 1462ae92a9..09eee32c74 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -65,6 +65,7 @@ namespace AppInstaller::Manifest::YamlWriter constexpr std::string_view InvocationParameter = "InvocationParameter"sv; constexpr std::string_view DisplayName = "DisplayName"sv; constexpr std::string_view MinimumOSVersion = "MinimumOSVersion"sv; + constexpr std::string_view DownloadCommandProhibited = "DownloadCommandProhibited"sv; // Installer switches constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; @@ -565,6 +566,7 @@ namespace AppInstaller::Manifest::YamlWriter WRITE_BOOL_PROPERTY(out, InstallLocationRequired, installer.InstallLocationRequired); WRITE_BOOL_PROPERTY(out, RequireExplicitUpgrade, installer.RequireExplicitUpgrade); WRITE_BOOL_PROPERTY(out, DisplayInstallWarnings, installer.DisplayInstallWarnings); + WRITE_BOOL_PROPERTY(out, DownloadCommandProhibited, installer.DownloadCommandProhibited); WRITE_PROPERTY_IF_EXISTS(out, MinimumOSVersion, installer.MinOSVersion); WRITE_PROPERTY_IF_EXISTS(out, ProductCode, installer.ProductCode); WRITE_PROPERTY_IF_EXISTS(out, UpgradeBehavior, UpdateBehaviorToString(installer.UpdateBehavior)); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 0f9112adaf..f8b2eb3ad0 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -111,5 +111,7 @@ namespace AppInstaller::Manifest MarketsInfo Markets; InstallationMetadataInfo InstallationMetadata; + + bool DownloadCommandProhibited = false; }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 94bd3ff17c..d3563dfa38 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -425,6 +425,8 @@ + + @@ -518,6 +520,8 @@ + + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index 89e1a6731d..411658d477 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -85,6 +85,12 @@ {f05e19bb-2161-4ab0-9d04-2dfa2d3eb3c6} + + {21da32f5-b918-436e-96a9-465525f90259} + + + {b2e78f3d-931e-432c-8485-255b1dbc9db7} + @@ -375,6 +381,12 @@ Public\winget + + Rest\Schema\1_6\Json + + + Rest\Schema\1_6 + @@ -596,6 +608,12 @@ Source Files + + Rest\Schema\1_6\Json + + + Rest\Schema\1_6 + diff --git a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp index b8290cd154..aa231c0a2e 100644 --- a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp +++ b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp @@ -6,6 +6,7 @@ #include "Rest/Schema/1_1/Json/ManifestDeserializer.h" #include "Rest/Schema/1_4/Json/ManifestDeserializer.h" #include "Rest/Schema/1_5/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_6/Json/ManifestDeserializer.h" namespace AppInstaller::Repository::JSON { @@ -36,10 +37,14 @@ namespace AppInstaller::Repository::JSON { m_pImpl->m_deserializer = std::make_unique(); } - else + else if (parts.size() > 1 && parts[1].Integer < 6) { m_pImpl->m_deserializer = std::make_unique(); } + else + { + m_pImpl->m_deserializer = std::make_unique(); + } } else { diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp index 405cdce7bb..cde94f2a16 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -6,6 +6,7 @@ #include "Rest/Schema/1_1/Interface.h" #include "Rest/Schema/1_4/Interface.h" #include "Rest/Schema/1_5/Interface.h" +#include "Rest/Schema/1_6/Interface.h" #include "Rest/Schema/HttpClientHelper.h" #include #include "Rest/Schema/InformationResponseDeserializer.h" @@ -19,7 +20,7 @@ using namespace AppInstaller::Utility; namespace AppInstaller::Repository::Rest { // Supported versions - std::set WingetSupportedContracts = { Version_1_0_0, Version_1_1_0, Version_1_4_0, Version_1_5_0 }; + std::set WingetSupportedContracts = { Version_1_0_0, Version_1_1_0, Version_1_4_0, Version_1_5_0, Version_1_6_0 }; constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv; constexpr size_t WindowsPackageManagerHeaderMaxLength = 1024; @@ -152,6 +153,10 @@ namespace AppInstaller::Repository::Rest { return std::make_unique(api, information, additionalHeaders); } + else if (version == Version_1_6_0) + { + return std::make_unique(api, information, additionalHeaders); + } THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h new file mode 100644 index 0000000000..8fb6bb1356 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_5/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_6 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_5::Interface + { + Interface(const std::string& restApi, IRestClient::Information information, const std::unordered_map& additionalHeaders = {}, const HttpClientHelper& httpClientHelper = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h new file mode 100644 index 0000000000..aaa4e69956 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_5/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_6::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_5::Json::ManifestDeserializer + { + protected: + + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp new file mode 100644 index 0000000000..75d3fb3b14 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_6::Json +{ + namespace + { + // Installer + constexpr std::string_view DownloadCommandProhibited = "DownloadCommandProhibited"sv; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_5::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + installer.DownloadCommandProhibited = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(DownloadCommandProhibited)).value_or(false); + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_6; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp new file mode 100644 index 0000000000..469d6a2ef6 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_6/Interface.h" +#include "Rest/Schema/IRestClient.h" +#include "Rest/Schema/HttpClientHelper.h" +#include "Rest/Schema/CommonRestConstants.h" +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_6 +{ + Interface::Interface( + const std::string& restApi, + IRestClient::Information information, + const std::unordered_map& additionalHeaders, + const HttpClientHelper& httpClientHelper) : V1_5::Interface(restApi, std::move(information), additionalHeaders, httpClientHelper) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_6_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_6_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h index 0aadc603b5..a89aa71e50 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h @@ -10,6 +10,7 @@ namespace AppInstaller::Repository::Rest::Schema const Utility::Version Version_1_1_0{ "1.1.0" }; const Utility::Version Version_1_4_0{ "1.4.0" }; const Utility::Version Version_1_5_0{ "1.5.0" }; + const Utility::Version Version_1_6_0{ "1.6.0" }; // General API response constants constexpr std::string_view Data = "Data"sv; diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp index a388f05674..29e6abecbf 100644 --- a/src/AppInstallerSharedLib/Errors.cpp +++ b/src/AppInstallerSharedLib/Errors.cpp @@ -228,6 +228,8 @@ namespace AppInstaller return "Application shutdown signal received"; case APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES: return "Failed to download package dependencies."; + case APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED: + return "Failed to download package. Download for offline installation is prohibited."; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index a8bf1fc875..33383538bd 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -120,6 +120,7 @@ #define APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB ((HRESULT)0x8A150069) #define APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED ((HRESULT)0x8A15006A) #define APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES ((HRESULT)0x8A15006B) +#define APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED ((HRESULT)0x8A15006C) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101)