From 8c6f3da6dc9e75ade4680a57045d1a1434359ff1 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Wed, 20 May 2026 11:18:28 -0700 Subject: [PATCH 01/18] Update rest source and wingetutil interop to include 1.28 manifest (#6234) Tests added. ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/6234) --- .../AppInstallerCLITests.vcxproj | 1 + .../AppInstallerCLITests.vcxproj.filters | 3 + src/AppInstallerCLITests/RestClient.cpp | 2 +- .../RestInterface_1_28.cpp | 440 ++++++++++++++++++ .../AppInstallerRepositoryCore.vcxproj | 4 + ...AppInstallerRepositoryCore.vcxproj.filters | 18 + .../ManifestJSONParser.cpp | 7 +- .../Rest/RestClient.cpp | 6 + .../Rest/Schema/1_28/Interface.h | 21 + .../Schema/1_28/Json/ManifestDeserializer.h | 17 + .../1_28/Json/ManifestDeserializer_1_28.cpp | 116 +++++ .../Rest/Schema/1_28/RestInterface_1_28.cpp | 26 ++ .../Rest/Schema/CommonRestConstants.h | 1 + .../ManifestUnitTest/V1ManifestReadTest.cs | 36 ++ .../TestCollateral/V1_28ManifestMerged.yaml | 296 ++++++++++++ .../WinGetUtilInterop.UnitTests.csproj | 3 + .../V1/InstallerDSCPowerShellModule.cs | 31 ++ .../V1/InstallerDSCPowerShellResource.cs | 19 + .../Manifest/V1/InstallerDSCv3.cs | 21 + .../Manifest/V1/InstallerDSCv3Resource.cs | 19 + .../V1/InstallerDesiredStateConfiguration.cs | 26 ++ src/WinGetUtilInterop/Manifest/V1/Manifest.cs | 25 +- .../Manifest/V1/ManifestInstaller.cs | 31 +- 23 files changed, 1144 insertions(+), 25 deletions(-) create mode 100644 src/AppInstallerCLITests/RestInterface_1_28.cpp create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_28/Interface.h create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer.h create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer_1_28.cpp create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_28/RestInterface_1_28.cpp create mode 100644 src/WinGetUtilInterop.UnitTests/TestCollateral/V1_28ManifestMerged.yaml create mode 100644 src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellModule.cs create mode 100644 src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellResource.cs create mode 100644 src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3.cs create mode 100644 src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3Resource.cs create mode 100644 src/WinGetUtilInterop/Manifest/V1/InstallerDesiredStateConfiguration.cs diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 1899bb36c7..b8bc46728c 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -286,6 +286,7 @@ + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index d4c0d58daa..61ae5cfea0 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -392,6 +392,9 @@ Source Files\Repository + + Source Files\Repository + Source Files\Common diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp index 97e1d54c91..46de28a7df 100644 --- a/src/AppInstallerCLITests/RestClient.cpp +++ b/src/AppInstallerCLITests/RestClient.cpp @@ -63,7 +63,7 @@ TEST_CASE("GetSupportedInterface", "[RestSource]") REQUIRE(RestClient::GetSupportedInterface(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.13.0" }; + Version invalid{ "1.29.0" }; REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, invalid, {}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); Authentication::AuthenticationArguments authArgs; diff --git a/src/AppInstallerCLITests/RestInterface_1_28.cpp b/src/AppInstallerCLITests/RestInterface_1_28.cpp new file mode 100644 index 0000000000..4286430f00 --- /dev/null +++ b/src/AppInstallerCLITests/RestInterface_1_28.cpp @@ -0,0 +1,440 @@ +// 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::Http; +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_28; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + 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", + "Repair": "/repair" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "deny", + "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, + "RepairBehavior": "uninstaller", + "ArchiveBinariesDependOnPath": true, + "Authentication": { + "AuthenticationType": "microsoftEntraId", + "MicrosoftEntraIdAuthenticationInfo" : { + "Resource": "TestResource", + "Scope" : "TestScope" + } + }, + "DesiredStateConfiguration": { + "PowerShell": [{ + "RepositoryUrl": "https://www.powershellgallery.com/api/v2", + "ModuleName": "TestModule", + "Resources": [{ + "Name": "TestResource" + }] + }], + "DSCv3": { + "Resources": [{ + "Type": "TestPublisher.TestProduct/TestResource" + }] + } + } + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const AppInstaller::Manifest::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 AppInstaller::Manifest::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() == 8); + 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.Switches.at(InstallerSwitchType::Repair) == "/repair"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); + 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); + REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); + REQUIRE(actualInstaller.ArchiveBinariesDependOnPath); + REQUIRE(actualInstaller.AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId); + REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo.has_value()); + REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo->Resource == "TestResource"); + REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo->Scope == "TestScope"); + + // DesiredStateConfiguration + REQUIRE(actualInstaller.DesiredStateConfiguration.size() == 2); + + // PowerShell entry + REQUIRE(actualInstaller.DesiredStateConfiguration[0].Type == DesiredStateConfigurationContainerType::PowerShell); + REQUIRE(actualInstaller.DesiredStateConfiguration[0].RepositoryURL == "https://www.powershellgallery.com/api/v2"); + REQUIRE(actualInstaller.DesiredStateConfiguration[0].ModuleName == "TestModule"); + REQUIRE(actualInstaller.DesiredStateConfiguration[0].Resources.size() == 1); + REQUIRE(actualInstaller.DesiredStateConfiguration[0].Resources[0].Name == "TestResource"); + + // DSCv3 entry + REQUIRE(actualInstaller.DesiredStateConfiguration[1].Type == DesiredStateConfigurationContainerType::DSCv3); + REQUIRE(actualInstaller.DesiredStateConfiguration[1].Resources.size() == 1); + REQUIRE(actualInstaller.DesiredStateConfiguration[1].Resources[0].Name == "TestPublisher.TestProduct/TestResource"); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_28", "[RestSource][Interface_1_28]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_28{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_28.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.28.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 53a8260f9c..a73d7d4ec2 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -378,6 +378,8 @@ + + @@ -480,6 +482,8 @@ + + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index d877dedeab..289d392c25 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -121,6 +121,12 @@ {2075b51e-aa11-473a-bae0-e0d4366f926b} + + {a3b7c8d1-e2f4-5a6b-9c0d-1e2f3a4b5c6d} + + + {b4c8d9e2-f3a5-6b7c-0d1e-2f3a4b5c6d7e} + @@ -501,6 +507,12 @@ Rest\Schema\1_12\Json + + Rest\Schema\1_28 + + + Rest\Schema\1_28\Json + Rest @@ -788,6 +800,12 @@ Rest\Schema\1_12\Json + + Rest\Schema\1_28 + + + Rest\Schema\1_28\Json + Rest diff --git a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp index 17386bbf54..1043dc83f5 100644 --- a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp +++ b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp @@ -11,6 +11,7 @@ #include "Rest/Schema/1_9/Json/ManifestDeserializer.h" #include "Rest/Schema/1_10/Json/ManifestDeserializer.h" #include "Rest/Schema/1_12/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_28/Json/ManifestDeserializer.h" namespace AppInstaller::Repository::JSON { @@ -61,10 +62,14 @@ namespace AppInstaller::Repository::JSON { m_pImpl->m_deserializer = std::make_unique(); } - else + else if (parts.size() > 1 && parts[1].Integer < 28) { 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 2c5f592c40..1202e91c33 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -12,6 +12,7 @@ #include "Rest/Schema/1_9/Interface.h" #include "Rest/Schema/1_10/Interface.h" #include "Rest/Schema/1_12/Interface.h" +#include "Rest/Schema/1_28/Interface.h" #include "Rest/Schema/InformationResponseDeserializer.h" #include "Rest/Schema/CommonRestConstants.h" #include @@ -37,6 +38,7 @@ namespace AppInstaller::Repository::Rest Version_1_9_0, Version_1_10_0, Version_1_12_0, + Version_1_28_0, }; constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv; @@ -216,6 +218,10 @@ namespace AppInstaller::Repository::Rest { return std::make_unique(api, helper, information, additionalHeaders, authArgs); } + else if (version == Version_1_28_0) + { + return std::make_unique(api, helper, information, additionalHeaders, authArgs); + } THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Interface.h new file mode 100644 index 0000000000..0fcc0a4fcd --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Interface.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_12/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_28 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_12::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); + + 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_28/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer.h new file mode 100644 index 0000000000..6466d6fe7a --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_12/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_28::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_12::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_28/Json/ManifestDeserializer_1_28.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer_1_28.cpp new file mode 100644 index 0000000000..4224c0a98d --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer_1_28.cpp @@ -0,0 +1,116 @@ +// 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_28::Json +{ + namespace + { + constexpr std::string_view DesiredStateConfiguration = "DesiredStateConfiguration"sv; + constexpr std::string_view PowerShell = "PowerShell"sv; + constexpr std::string_view DSCv3 = "DSCv3"sv; + constexpr std::string_view RepositoryUrl = "RepositoryUrl"sv; + constexpr std::string_view ModuleName = "ModuleName"sv; + constexpr std::string_view Resources = "Resources"sv; + constexpr std::string_view Name = "Name"sv; + constexpr std::string_view Type = "Type"sv; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_12::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + // DesiredStateConfiguration + auto dscNode = JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(DesiredStateConfiguration)); + if (dscNode) + { + const auto& dscObject = dscNode->get(); + if (!dscObject.is_null() && dscObject.is_object()) + { + // PowerShell DSC modules + auto powerShellNode = JSON::GetRawJsonArrayFromJsonNode(dscObject, JSON::GetUtilityString(PowerShell)); + if (powerShellNode) + { + for (auto const& moduleNode : powerShellNode->get()) + { + std::optional repositoryUrl = JSON::GetRawStringValueFromJsonNode(moduleNode, JSON::GetUtilityString(RepositoryUrl)); + std::optional moduleName = JSON::GetRawStringValueFromJsonNode(moduleNode, JSON::GetUtilityString(ModuleName)); + + if (!JSON::IsValidNonEmptyStringValue(repositoryUrl) || !JSON::IsValidNonEmptyStringValue(moduleName)) + { + AICLI_LOG(Repo, Error, << "Missing required fields in DesiredStateConfiguration PowerShell entry."); + continue; + } + + std::vector resources; + auto resourcesNode = JSON::GetRawJsonArrayFromJsonNode(moduleNode, JSON::GetUtilityString(Resources)); + if (resourcesNode) + { + for (auto const& resourceNode : resourcesNode->get()) + { + std::optional name = JSON::GetRawStringValueFromJsonNode(resourceNode, JSON::GetUtilityString(Name)); + if (JSON::IsValidNonEmptyStringValue(name)) + { + DesiredStateConfigurationResourceInfo resourceInfo; + resourceInfo.Name = std::move(*name); + resources.emplace_back(std::move(resourceInfo)); + } + } + } + + if (!resources.empty()) + { + installer.DesiredStateConfiguration.emplace_back(std::move(*repositoryUrl), std::move(*moduleName), std::move(resources)); + } + } + } + + // DSCv3 resources + auto dscv3Node = JSON::GetJsonValueFromNode(dscObject, JSON::GetUtilityString(DSCv3)); + if (dscv3Node) + { + const auto& dscv3Object = dscv3Node->get(); + if (!dscv3Object.is_null() && dscv3Object.is_object()) + { + std::vector resources; + auto resourcesNode = JSON::GetRawJsonArrayFromJsonNode(dscv3Object, JSON::GetUtilityString(Resources)); + if (resourcesNode) + { + for (auto const& resourceNode : resourcesNode->get()) + { + std::optional type = JSON::GetRawStringValueFromJsonNode(resourceNode, JSON::GetUtilityString(Type)); + if (JSON::IsValidNonEmptyStringValue(type)) + { + DesiredStateConfigurationResourceInfo resourceInfo; + resourceInfo.Name = std::move(*type); + resources.emplace_back(std::move(resourceInfo)); + } + } + } + + if (!resources.empty()) + { + installer.DesiredStateConfiguration.emplace_back(std::move(resources)); + } + } + } + } + } + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_28; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_28/RestInterface_1_28.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/RestInterface_1_28.cpp new file mode 100644 index 0000000000..d2381e85f5 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/RestInterface_1_28.cpp @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_28/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_28 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, + Authentication::AuthenticationArguments authArgs) : V1_12::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders, std::move(authArgs)) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_28_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_28_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h index 97a3806eff..2454e33e3e 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h @@ -15,6 +15,7 @@ namespace AppInstaller::Repository::Rest::Schema const Utility::Version Version_1_9_0{ "1.9.0" }; const Utility::Version Version_1_10_0{ "1.10.0" }; const Utility::Version Version_1_12_0{ "1.12.0" }; + const Utility::Version Version_1_28_0{ "1.28.0" }; // General API response constants constexpr std::string_view Data = "Data"sv; diff --git a/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs b/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs index 1c3eefaca4..d01b309dad 100644 --- a/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs +++ b/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs @@ -38,6 +38,7 @@ private enum TestManifestVersion V1_9_0, V1_10_0, V1_12_0, + V1_28_0, } /// @@ -81,6 +82,11 @@ public void ReadV1ManifestsAndVerifyContents() Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_12_0ManifestMerged)); this.ValidateManifestFields(v1_12_0manifest, TestManifestVersion.V1_12_0); + + Manifest v1_28_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_28_0ManifestMerged)); + + this.ValidateManifestFields(v1_28_0manifest, TestManifestVersion.V1_28_0); } /// @@ -500,6 +506,31 @@ private void ValidateManifestFields(Manifest manifest, TestManifestVersion manif Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx64.exe", installer4.Url); Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer4.Sha256); } + + if (manifestVersion >= TestManifestVersion.V1_28_0) + { + // Root level DesiredStateConfiguration + Assert.NotNull(manifest.DesiredStateConfiguration); + Assert.Single(manifest.DesiredStateConfiguration.PowerShell); + Assert.Equal("https://www.powershellgallery.com/api/v2", manifest.DesiredStateConfiguration.PowerShell[0].RepositoryUrl); + Assert.Equal("DefaultTestModule", manifest.DesiredStateConfiguration.PowerShell[0].ModuleName); + Assert.Single(manifest.DesiredStateConfiguration.PowerShell[0].Resources); + Assert.Equal("DefaultTestResource", manifest.DesiredStateConfiguration.PowerShell[0].Resources[0].Name); + Assert.NotNull(manifest.DesiredStateConfiguration.DSCv3); + Assert.Single(manifest.DesiredStateConfiguration.DSCv3.Resources); + Assert.Equal("DefaultPublisher.DefaultProduct/DefaultResource", manifest.DesiredStateConfiguration.DSCv3.Resources[0].Type); + + // Installer level DesiredStateConfiguration + Assert.NotNull(installer1.DesiredStateConfiguration); + Assert.Single(installer1.DesiredStateConfiguration.PowerShell); + Assert.Equal("https://www.powershellgallery.com/api/v2", installer1.DesiredStateConfiguration.PowerShell[0].RepositoryUrl); + Assert.Equal("TestModule", installer1.DesiredStateConfiguration.PowerShell[0].ModuleName); + Assert.Single(installer1.DesiredStateConfiguration.PowerShell[0].Resources); + Assert.Equal("TestResource", installer1.DesiredStateConfiguration.PowerShell[0].Resources[0].Name); + Assert.NotNull(installer1.DesiredStateConfiguration.DSCv3); + Assert.Single(installer1.DesiredStateConfiguration.DSCv3.Resources); + Assert.Equal("TestPublisher.TestProduct/TestResource", installer1.DesiredStateConfiguration.DSCv3.Resources[0].Type); + } } /// @@ -542,6 +573,11 @@ internal class ManifestStrings /// Merged v1.12 manifest. /// public const string V1_12_0ManifestMerged = "V1_12ManifestMerged.yaml"; + + /// + /// Merged v1.28 manifest. + /// + public const string V1_28_0ManifestMerged = "V1_28ManifestMerged.yaml"; #pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore /// diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_28ManifestMerged.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_28ManifestMerged.yaml new file mode 100644 index 0000000000..06d16a1152 --- /dev/null +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_28ManifestMerged.yaml @@ -0,0 +1,296 @@ +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: zip +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + ReturnResponseUrl: https://defaultReturnResponseUrl.com + - InstallerReturnCode: 3 + ReturnResponse: custom +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true +RepairBehavior: uninstaller +Authentication: + AuthenticationType: microsoftEntraId + MicrosoftEntraIdAuthenticationInfo: + Resource: DefaultResource + Scope: DefaultScope +DesiredStateConfiguration: + PowerShell: + - RepositoryUrl: https://www.powershellgallery.com/api/v2 + ModuleName: DefaultTestModule + Resources: + - Name: DefaultTestResource + DSCv3: + Resources: + - Type: DefaultPublisher.DefaultProduct/DefaultResource +Localization: +- Agreements: + - Agreement: Text + AgreementLabel: Label + AgreementUrl: https://AgreementUrl.net + Author: Microsoft UK + Copyright: Copyright Microsoft Corporation UK + CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK + Description: The MSIX SDK project is an effort to enable developers UK + License: MIT License UK + LicenseUrl: https://www.microsoft.com/msixsdk/license/UK + PackageLocale: en-GB + PackageName: MSIX SDK UK + PackageUrl: https://www.microsoft.com/msixsdk/home/UK + PrivacyUrl: https://www.microsoft.com/privacy/UK + Publisher: Microsoft UK + PublisherSupportUrl: https://www.microsoft.com/support/UK + PublisherUrl: https://www.microsoft.com/UK + ReleaseNotes: Release notes + ReleaseNotesUrl: https://ReleaseNotes.net + ShortDescription: This is MSIX SDK UK + Tags: + - appxsdkUK + - msixsdkUK + PurchaseUrl: https://PurchaseUrl.com + InstallationNotes: Installation notes + Documentations: + - DocumentLabel: Document label + DocumentUrl: https://DocumentUrl.com + Icons: + - IconUrl: https://testIcon2 + IconFileType: png + IconResolution: 32x32 + IconTheme: dark + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath2 + PortableCommandAlias: PortableCommandAlias2 + RequireExplicitUpgrade: false + DisplayInstallWarnings: true + ElevationRequirement: elevationRequired + NestedInstallerType: msi + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + ReturnResponseUrl: https://returnResponseUrl.com + DownloadCommandProhibited: true + ArchiveBinariesDependOnPath: false + RepairBehavior: modify + InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main2.exe" + FileSha256: 79D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch2 + InvocationParameter: "/arg2" + DisplayName: "DisplayName2" + Authentication: + AuthenticationType: microsoftEntraId + MicrosoftEntraIdAuthenticationInfo: + Resource: Resource + Scope: Scope + DesiredStateConfiguration: + PowerShell: + - RepositoryUrl: https://www.powershellgallery.com/api/v2 + ModuleName: TestModule + Resources: + - Name: TestResource + DSCv3: + Resources: + - Type: TestPublisher.TestProduct/TestResource + - Architecture: x64 + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerType: exe + ProductCode: '{Bar}' + MSStoreProductIdentifier: fakeIdentifier + - Architecture: neutral + InstallerType: zip + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + NestedInstallerType: font + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1.otf + - RelativeFilePath: relativeFilePath2.ttf + - RelativeFilePath: relativeFilePath3.fnt + - RelativeFilePath: relativeFilePath4.ttc + - RelativeFilePath: relativeFilePath5.otc + - Architecture: neutral + InstallerType: font + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 +ManifestType: merged +ManifestVersion: 1.28.0 diff --git a/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj b/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj index 03037a1a99..2a2b444ee5 100644 --- a/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj +++ b/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj @@ -102,6 +102,9 @@ Always + + Always + PreserveNewest diff --git a/src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellModule.cs b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellModule.cs new file mode 100644 index 0000000000..6bd0fadae2 --- /dev/null +++ b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellModule.cs @@ -0,0 +1,31 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Models.V1 +{ + using System.Collections.Generic; + + /// + /// PowerShell DSC module item. + /// + public class InstallerDSCPowerShellModule + { + /// + /// Gets or sets the repository URL. + /// + public string RepositoryUrl { get; set; } + + /// + /// Gets or sets the module name. + /// + public string ModuleName { get; set; } + + /// + /// Gets or sets the list of resources contained in the module. + /// + public List Resources { get; set; } + } +} diff --git a/src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellResource.cs b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellResource.cs new file mode 100644 index 0000000000..9883b61822 --- /dev/null +++ b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCPowerShellResource.cs @@ -0,0 +1,19 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Models.V1 +{ + /// + /// PowerShell DSC resource item. + /// + public class InstallerDSCPowerShellResource + { + /// + /// Gets or sets the name of the resource. + /// + public string Name { get; set; } + } +} diff --git a/src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3.cs b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3.cs new file mode 100644 index 0000000000..3ce552f81f --- /dev/null +++ b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Models.V1 +{ + using System.Collections.Generic; + + /// + /// DSCv3 resource info. + /// + public class InstallerDSCv3 + { + /// + /// Gets or sets the list of DSCv3 resources. + /// + public List Resources { get; set; } + } +} diff --git a/src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3Resource.cs b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3Resource.cs new file mode 100644 index 0000000000..d831ef29fa --- /dev/null +++ b/src/WinGetUtilInterop/Manifest/V1/InstallerDSCv3Resource.cs @@ -0,0 +1,19 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Models.V1 +{ + /// + /// DSCv3 resource item. + /// + public class InstallerDSCv3Resource + { + /// + /// Gets or sets the type of the resource. + /// + public string Type { get; set; } + } +} diff --git a/src/WinGetUtilInterop/Manifest/V1/InstallerDesiredStateConfiguration.cs b/src/WinGetUtilInterop/Manifest/V1/InstallerDesiredStateConfiguration.cs new file mode 100644 index 0000000000..24fe213ecb --- /dev/null +++ b/src/WinGetUtilInterop/Manifest/V1/InstallerDesiredStateConfiguration.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Models.V1 +{ + using System.Collections.Generic; + + /// + /// Desired state configuration info for an installer. + /// + public class InstallerDesiredStateConfiguration + { + /// + /// Gets or sets the list of PowerShell DSC module items. + /// + public List PowerShell { get; set; } + + /// + /// Gets or sets the DSCv3 resource info. + /// + public InstallerDSCv3 DSCv3 { get; set; } + } +} diff --git a/src/WinGetUtilInterop/Manifest/V1/Manifest.cs b/src/WinGetUtilInterop/Manifest/V1/Manifest.cs index 9df3343f84..9c5908bd9a 100644 --- a/src/WinGetUtilInterop/Manifest/V1/Manifest.cs +++ b/src/WinGetUtilInterop/Manifest/V1/Manifest.cs @@ -317,28 +317,33 @@ public class Manifest /// /// Gets or sets the default list of installer expected return codes. /// - public List ExpectedReturnCodes { get; set; } - + public List ExpectedReturnCodes { get; set; } + /// /// Gets or sets a value indicating whether the installer is prohibited from being downloaded for offline installation. /// - public bool? DownloadCommandProhibited { get; set; } - - /// - /// Gets or sets a value indicating whether the install location should be added directly to the PATH environment variable. - /// + public bool? DownloadCommandProhibited { get; set; } + + /// + /// Gets or sets a value indicating whether the install location should be added directly to the PATH environment variable. + /// public bool? ArchiveBinariesDependOnPath { get; set; } /// /// Gets or sets the default repair behavior. /// - public string RepairBehavior { get; set; } - + public string RepairBehavior { get; set; } + /// /// Gets or sets the default installer authentication info. - /// + /// public InstallerAuthentication Authentication { get; set; } + /// + /// Gets or sets the default desired state configuration info. + /// + public InstallerDesiredStateConfiguration DesiredStateConfiguration { get; set; } + /// /// Gets or sets collection of ManifestInstaller. At least one is required. /// diff --git a/src/WinGetUtilInterop/Manifest/V1/ManifestInstaller.cs b/src/WinGetUtilInterop/Manifest/V1/ManifestInstaller.cs index 172bb0de58..d38b10e1ad 100644 --- a/src/WinGetUtilInterop/Manifest/V1/ManifestInstaller.cs +++ b/src/WinGetUtilInterop/Manifest/V1/ManifestInstaller.cs @@ -35,15 +35,15 @@ public class ManifestInstaller /// /// Gets or sets the signature SHA256 for an appx/msix. Only used by appx/msix type. /// - public string SignatureSha256 { get; set; } - + public string SignatureSha256 { get; set; } + /// /// Gets or sets the Store ProductId. Only used when InstallerType is MSStore. - /// + /// [YamlMember(Alias = "MSStoreProductIdentifier")] public string ProductId { get; set; } - - // Common installer fields that may have defaults in manifest root level. + + // Common installer fields that may have defaults in manifest root level. /// /// Gets or sets the installer locale. @@ -204,23 +204,28 @@ public class ManifestInstaller /// /// Gets or sets a value indicating whether the installer is prohibited from being downloaded for offline installation. /// - public bool? DownloadCommandProhibited { get; set; } - - /// - /// Gets or sets a value indicating whether the install location should be added directly to the PATH environment variable. - /// + public bool? DownloadCommandProhibited { get; set; } + + /// + /// Gets or sets a value indicating whether the install location should be added directly to the PATH environment variable. + /// public bool? ArchiveBinariesDependOnPath { get; set; } /// /// Gets or sets the repair behavior. /// - public string RepairBehavior { get; set; } - + public string RepairBehavior { get; set; } + /// /// Gets or sets the installer authentication info. - /// + /// public InstallerAuthentication Authentication { get; set; } + /// + /// Gets or sets the desired state configuration info. + /// + public InstallerDesiredStateConfiguration DesiredStateConfiguration { get; set; } + /// /// Returns a List of strings containing the URIs contained within this installer. /// From dcd3581a2ef08c17e10574079b33070f66f280a3 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Wed, 20 May 2026 13:32:31 -0700 Subject: [PATCH 02/18] Move pre-check errors to post-check (#6236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description We had some pre-check errors for various issues in the OS. Move those to attempt the operation and convert any failures in the scenario on the older OS to the well-known HRESULT. --- .../Workflows/InstallFlow.cpp | 22 ++++++++++--------- .../Workflows/UninstallFlow.cpp | 22 ++++++++++--------- src/AppInstallerCommonCore/MSStore.cpp | 21 ++++++++++-------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 9829694695..252d76125a 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -211,16 +211,6 @@ namespace AppInstaller::CLI::Workflow bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; - // TODO: There was a bug in deployment api if provision api was called in packaged context. - // Remove this check when the OS bug is fixed and back ported. - if (isMachineScope && Runtime::IsRunningInPackagedContext()) - { - context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; - context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - AICLI_LOG(CLI, Error, << "Device wide install for msix type is not supported in packaged context."); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); - } - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; bool registrationDeferred = false; @@ -241,6 +231,18 @@ namespace AppInstaller::CLI::Workflow } catch (const wil::ResultException& re) { + // There was a bug in the deployment provision API when called in a packaged context, + // fixed in 10.0.26100.0. On older OS versions, convert any failure under these conditions + // to the error that was previously always returned. + if (isMachineScope && Runtime::IsRunningInPackagedContext() && + !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) + { + context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; + context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + AICLI_LOG(CLI, Error, << "Device wide install for msix type is not supported in packaged context on this OS version. Error: " << re.GetErrorCode()); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + context.Add(re.GetErrorCode()); context << ReportInstallerResult("MSIX"sv, re.GetErrorCode(), /* isHResult */ true); return; diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index 55bbe18d6d..bd6e105a62 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -366,16 +366,6 @@ namespace AppInstaller::CLI::Workflow { bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; - // TODO: There was a bug in deployment api if deprovision api was called in packaged context. - // Remove this check when the OS bug is fixed and back ported. - if (isMachineScope && Runtime::IsRunningInPackagedContext()) - { - context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; - context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - AICLI_LOG(CLI, Error, << "Device wide uninstall for msix type is not supported in packaged context."); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); - } - const auto& packageFamilyNames = context.Get(); context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; @@ -402,6 +392,18 @@ namespace AppInstaller::CLI::Workflow } catch (const wil::ResultException& re) { + // There was a bug in the deployment deprovision API when called in a packaged context, + // fixed in 10.0.26100.0. On older OS versions, convert any failure under these conditions + // to the error that was previously always returned. + if (isMachineScope && Runtime::IsRunningInPackagedContext() && + !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) + { + context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; + context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + AICLI_LOG(CLI, Error, << "Device wide uninstall for msix type is not supported in packaged context on this OS version. Error: " << re.GetErrorCode()); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + context.Add(re.GetErrorCode()); context << ReportUninstallerResult("MSIXUninstall"sv, re.GetErrorCode(), /* isHResult */ true); return; diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp index 29e0d161b5..de0623fac2 100644 --- a/src/AppInstallerCommonCore/MSStore.cpp +++ b/src/AppInstallerCommonCore/MSStore.cpp @@ -401,15 +401,6 @@ namespace AppInstaller::MSStore if (m_scope == Manifest::ScopeEnum::Machine) { - // TODO: There was a bug in InstallService where admin user is incorrectly identified as not admin, - // causing false access denied on many OS versions. - // Remove this check when the OS bug is fixed and back ported. - if (!Runtime::IsRunningAsSystem()) - { - AICLI_LOG(Core, Error, << "Device wide install for msstore type is not supported under admin context."); - return APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED; - } - installOptions.InstallForAllUsers(true); } @@ -438,6 +429,18 @@ namespace AppInstaller::MSStore HRESULT hr = WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); + // There was a bug in InstallService where admin users were incorrectly identified as non-admin, + // causing false access denied errors; fixed in 10.0.26100.0. On older OS versions, convert any + // failure under these conditions to the error that was previously always returned. + if (FAILED(hr) && + m_scope == Manifest::ScopeEnum::Machine && + !Runtime::IsRunningAsSystem() && + !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) + { + AICLI_LOG(Core, Error, << "Device wide install for msstore type is not supported under admin context on this OS version. Error: " << hr); + return APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED; + } + bool shouldProvision = m_scope == Manifest::ScopeEnum::Machine; #ifndef AICLI_DISABLE_TEST_HOOKS if (TestHooks::s_ProvisionAfterInstall) From cf184b702a9eb8425a0b6137d40b568950969f49 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Wed, 20 May 2026 15:26:04 -0700 Subject: [PATCH 03/18] In-proc certificate pinning validation override (#6233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description Adds the ability to override the certificate pinning validation with their own handler for in-proc COM callers. In-proc callers can provide a handler delegate on their `PackageCatalogReference` object(s) before calling `Connect` and will receive a callback when winget would execute a certificate pinning validation. If they accept the server connection, the certificate will be cached as it is with the internal check and automatically approved for any future connections. Note that `Connect` may not actually trigger the callback if the `/information` for the catalog is already cached, but you must set the callback before the `Connect` call for it to be attached to the connected catalog for future use. The callback can only be set for the MS Store catalog when the group policy `BypassCertificatePinningForMicrosoftStore` is not configured. --- .../ConnectionValidationSample.csproj | 51 +++ samples/ConnectionValidationSample/Program.cs | 116 ++++++ .../Commands/RootCommand.cpp | 12 +- src/AppInstallerCLIE2ETests/Constants.cs | 3 + .../GroupPolicyHelper.cs | 31 ++ .../Interop/ConnectionValidationInterop.cs | 343 ++++++++++++++++++ .../TestData/TestRestSource/information | 6 + src/AppInstallerCommonCore/AdminSettings.cpp | 4 +- src/AppInstallerRepositoryCore/ISource.h | 5 + .../Public/winget/RepositorySource.h | 7 +- .../RepositorySource.cpp | 10 +- .../Rest/RestSourceFactory.cpp | 13 +- src/AppInstallerRepositoryCore/SourceList.cpp | 4 +- .../SourcePolicy.cpp | 4 +- src/AppInstallerSharedLib/Certificates.cpp | 35 +- src/AppInstallerSharedLib/GroupPolicy.cpp | 35 +- .../Public/winget/Certificates.h | 43 ++- .../Public/winget/GroupPolicy.h | 35 +- src/LocalhostWebServer/Startup.cs | 13 +- ...ft.Management.Deployment.Projection.csproj | 26 +- .../Helpers.cpp | 6 + src/Microsoft.Management.Deployment/Helpers.h | 1 + .../Microsoft.Management.Deployment.vcxproj | 2 + ...soft.Management.Deployment.vcxproj.filters | 2 + ...geCatalogConnectionValidationEventArgs.cpp | 18 + ...kageCatalogConnectionValidationEventArgs.h | 23 ++ .../PackageCatalogReference.cpp | 63 ++++ .../PackageCatalogReference.h | 9 +- .../PackageManager.idl | 37 ++ src/Microsoft.Management.Deployment/pch.h | 2 + src/WindowsPackageManager/main.cpp | 9 + 31 files changed, 907 insertions(+), 61 deletions(-) create mode 100644 samples/ConnectionValidationSample/ConnectionValidationSample.csproj create mode 100644 samples/ConnectionValidationSample/Program.cs create mode 100644 src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs create mode 100644 src/AppInstallerCLIE2ETests/TestData/TestRestSource/information create mode 100644 src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp create mode 100644 src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h diff --git a/samples/ConnectionValidationSample/ConnectionValidationSample.csproj b/samples/ConnectionValidationSample/ConnectionValidationSample.csproj new file mode 100644 index 0000000000..7da59dfe3d --- /dev/null +++ b/samples/ConnectionValidationSample/ConnectionValidationSample.csproj @@ -0,0 +1,51 @@ + + + + Exe + net8.0-windows10.0.26100.0 + 10.0.17763.0 + enable + enable + x64;x86;arm64 + + x64 + Debug + + $([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\..\src\$(WinGetBuildPlatform)\$(WinGetBuildConfiguration)')) + + + + + + + + + 10.0.26100.0 + Microsoft.Management.Deployment + + + + + + + + + + PreserveNewest + Microsoft.Management.Deployment.dll + + + PreserveNewest + WindowsPackageManager.dll + + + PreserveNewest + WindowsPackageManager.pdb + + + + \ No newline at end of file diff --git a/samples/ConnectionValidationSample/Program.cs b/samples/ConnectionValidationSample/Program.cs new file mode 100644 index 0000000000..2cb05abeb8 --- /dev/null +++ b/samples/ConnectionValidationSample/Program.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Sample demonstrating the ConnectionValidationHandler on PackageCatalogReference. +// +// This sample retrieves a named package catalog, installs a connection validation +// callback that shows certificate information, prompts the user to accept or reject +// the certificate, then reports the Connect() result. +// +// Usage: ConnectionValidationSample +// Example: ConnectionValidationSample winget +// +// Requirements: +// - Must run in-process (the ConnectionValidationHandler setter rejects out-of-proc callers). +// - The Microsoft.Management.Deployment COM server must be registered (deploy the dev package +// or install WinGet from the Microsoft Store). + +using Microsoft.Management.Deployment; +using Windows.Security.Cryptography.Certificates; + +if (args.Length == 0) +{ + Console.Error.WriteLine("Usage: ConnectionValidationSample "); + Console.Error.WriteLine("Example: ConnectionValidationSample winget"); + return 1; +} + +string catalogName = args[0]; + +// Use in-proc activation so that ConnectionValidationHandler can be set. +// CsWinRT activates PackageManager in-proc via DllGetActivationFactory from +// Microsoft.Management.Deployment.dll placed alongside this executable. +var packageManager = new PackageManager(); + +var catalogRef = packageManager.GetPackageCatalogByName(catalogName); +if (catalogRef is null) +{ + Console.Error.WriteLine($"No catalog named '{catalogName}' found."); + Console.Error.WriteLine("Use 'winget source list' to see available catalogs."); + return 1; +} + +Console.WriteLine($"Catalog: {catalogRef.Info.Name} ({catalogRef.Info.Argument})"); +Console.WriteLine("Setting connection validation handler..."); + +catalogRef.ConnectionValidationHandler = (PackageCatalogConnectionValidationEventArgs validationArgs) => +{ + Certificate cert = validationArgs.ServerCertificate; + + Console.WriteLine(); + Console.WriteLine("Catalog connection validation for: " + catalogRef.Info.Name); + Console.WriteLine(" at: " + catalogRef.Info.Argument); + Console.WriteLine(); + Console.WriteLine("=== Server Certificate ==="); + Console.WriteLine($" Subject: {cert.Subject}"); + Console.WriteLine($" Issuer: {cert.Issuer}"); + Console.WriteLine($" Valid from: {cert.ValidFrom}"); + Console.WriteLine($" Valid to: {cert.ValidTo}"); + Console.WriteLine($" Serial: {cert.SerialNumber}"); + Console.WriteLine("=========================="); + Console.WriteLine(); + Console.Write("Accept this certificate? [Y/N]: "); + + while (true) + { + string? input = Console.ReadLine()?.Trim().ToUpperInvariant(); + if (input == "Y") + { + Console.WriteLine("Certificate accepted."); + return PackageCatalogConnectionValidationResult.Ok; + } + else if (input == "N") + { + Console.WriteLine("Certificate rejected."); + return PackageCatalogConnectionValidationResult.CertificateRejected; + } + else + { + Console.Write("Please enter Y or N: "); + } + } +}; + +Console.WriteLine("Connecting..."); +ConnectResult result = catalogRef.Connect(); + +Console.WriteLine(); +Console.WriteLine($"Connect result: {result.Status}"); + +if (result.Status == ConnectResultStatus.Ok) +{ + Console.WriteLine($"Successfully connected to '{catalogName}'."); + + // Run a simple search to force a live network call, since Connect() may serve a cached response. + Console.WriteLine("Searching to verify live connection..."); + var findOptions = new FindPackagesOptions(); + findOptions.Selectors.Add(new PackageMatchFilter + { + Field = PackageMatchField.Id, + Option = PackageFieldMatchOption.StartsWithCaseInsensitive, + Value = "Microsoft.", + }); + var searchResult = result.PackageCatalog.FindPackages(findOptions); + Console.WriteLine($"Search result: {searchResult.Status} ({searchResult.Matches.Count} match(es))"); + + return 0; +} +else +{ + Console.Error.WriteLine($"Failed to connect to '{catalogName}'."); + if (result.ExtendedErrorCode is not null) + { + Console.Error.WriteLine($" Error code: 0x{result.ExtendedErrorCode.HResult:X8}"); + } + return 1; +} diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index d0bff5a55b..39dc8a8ac2 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -102,20 +102,20 @@ namespace AppInstaller::CLI if (groupPolicies.GetState(Settings::TogglePolicy::Policy::AdditionalSources) == Settings::PolicyState::Enabled) { info << std::endl; - auto sources = groupPolicies.GetValueRef(); - if (sources.has_value() && !sources->get().empty()) + auto sources = groupPolicies.GetValue(); + if (sources.has_value() && !sources->empty()) { - OutputGroupPolicySourceList(context, sources->get(), Resource::String::SourceListAdditionalSource); + OutputGroupPolicySourceList(context, sources.value(), Resource::String::SourceListAdditionalSource); } } if (groupPolicies.GetState(Settings::TogglePolicy::Policy::AllowedSources) == Settings::PolicyState::Enabled) { info << std::endl; - auto sources = groupPolicies.GetValueRef(); - if (sources.has_value() && !sources->get().empty()) + auto sources = groupPolicies.GetValue(); + if (sources.has_value() && !sources->empty()) { - OutputGroupPolicySourceList(context, sources->get(), Resource::String::SourceListAllowedSource); + OutputGroupPolicySourceList(context, sources.value(), Resource::String::SourceListAllowedSource); } } info << std::endl; diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index bdc9f3bf29..ddc4911a39 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -47,6 +47,9 @@ public class Constants public const string TestSourceUrl = @"https://localhost:5001/TestKit"; public const string TestSourceType = "Microsoft.PreIndexed.Package"; public const string TestSourceIdentifier = @"WingetE2E.Tests_8wekyb3d8bbwe"; + public const string RestTestSourceName = @"TestRestSource"; + public const string RestTestSourceUrl = @"https://localhost:5001/TestKit/TestData/TestRestSource"; + public const string RestTestSourceType = "Microsoft.Rest"; public const string AICLIPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; public const string AICLIPackageName = "WinGetDevCLI"; diff --git a/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs b/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs index b18388745c..28bcdf2e7c 100644 --- a/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs +++ b/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs @@ -10,6 +10,7 @@ namespace AppInstallerCLIE2ETests using System.Collections.Generic; using System.IO; using System.Linq; + using System.Runtime.InteropServices; using System.Xml.Linq; using AppInstallerCLIE2ETests.Helpers; using Microsoft.Win32; @@ -133,6 +134,11 @@ private GroupPolicyHelper(string name, string elementId) /// public static GroupPolicyHelper EnableConfigurationProcessorPath { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerConfigurationProcessorPath"); + /// + /// Gets the Bypass certificate pinning for Microsoft Store policy. + /// + public static GroupPolicyHelper BypassCertificatePinningForMicrosoftStore { get; private set; } = new GroupPolicyHelper("EnableBypassCertificatePinningForMicrosoftStore"); + /// /// Gets the Enable auto update interval policy. /// @@ -231,6 +237,8 @@ public void Enable() { key.SetValue(this.ValueName, enabledValue); } + + ReloadGroupPolicyIfAvailable(); } /// @@ -249,6 +257,8 @@ public void Disable() { key.SetValue(this.ValueName, disabledValue); } + + ReloadGroupPolicyIfAvailable(); } /// @@ -283,6 +293,8 @@ public void SetNotConfigured() } } } + + ReloadGroupPolicyIfAvailable(); } /// @@ -339,6 +351,25 @@ public void SetEnabledList(IEnumerable values) this.SetEnabledList(values.Select(source => JsonConvert.SerializeObject(source))); } + [DllImport("WindowsPackageManager.dll", CallingConvention = CallingConvention.StdCall)] + private static extern int WindowsPackageManagerTestHook_ReloadGroupPolicy(); + + /// + /// Calls the in-process test hook to reload the GroupPolicy singleton from the current registry state. + /// Silently ignored if the DLL is not loaded in this process (e.g., out-of-process test scenarios). + /// + private static void ReloadGroupPolicyIfAvailable() + { + try + { + WindowsPackageManagerTestHook_ReloadGroupPolicy(); + } + catch (Exception) + { + // The DLL is not loaded in this process (out-of-process scenario); nothing to do. + } + } + /// /// Gets the value from a "decimal" child element. /// diff --git a/src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs b/src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs new file mode 100644 index 0000000000..814be7190a --- /dev/null +++ b/src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs @@ -0,0 +1,343 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + using Windows.Security.Cryptography.Certificates; + using Windows.Storage.Streams; + + /// + /// Tests for the in-proc connection validation callback on PackageCatalogReference. + /// These tests require an in-process COM server because ConnectionValidationHandler + /// can only be set by in-proc callers. + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + public class ConnectionValidationInterop : BaseInterop + { + private PackageManager packageManager; + + /// + /// Initializes a new instance of the class. + /// + /// initializer. + public ConnectionValidationInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Set up before each test: create a package manager and ensure the REST source is not present. + /// + [SetUp] + public void Setup() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.SetNotConfigured(); + TestCommon.RunAICLICommand("source remove", Constants.RestTestSourceName); + this.packageManager = this.TestFactory.CreatePackageManager(); + } + + /// + /// Tear down after each test: remove the REST source if it was added. + /// + [TearDown] + public void TearDown() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.SetNotConfigured(); + TestCommon.RunAICLICommand("source remove", Constants.RestTestSourceName); + } + + /// + /// Verifies that the connection validation callback is invoked with a non-null server certificate, + /// that returning Ok allows the connection to succeed, and that the received certificate matches + /// the known test server certificate. + /// + /// The task. + [Test] + public async Task ConnectionValidationCallback_AllowsConnection_ConnectSucceeds() + { + var catalogRef = await this.AddRestCatalogAsync(); + + byte[] receivedCertDer = null; + catalogRef.ConnectionValidationHandler = (args) => + { + receivedCertDer = GetCertificateDerBytes(args.ServerCertificate); + return PackageCatalogConnectionValidationResult.Ok; + }; + + var connectResult = catalogRef.Connect(); + + Assert.IsNotNull(connectResult); + Assert.AreEqual(ConnectResultStatus.Ok, connectResult.Status, "Connect should succeed when handler returns Ok."); + Assert.IsNotNull(connectResult.PackageCatalog, "PackageCatalog should be non-null on success."); + Assert.IsNotNull(receivedCertDer, "Handler should have been called with a non-null certificate."); + + // Verify the received certificate matches the test server's known certificate. + if (!string.IsNullOrEmpty(TestSetup.Parameters.LocalServerCertPath) && + File.Exists(TestSetup.Parameters.LocalServerCertPath)) + { + byte[] expectedCertDer = File.ReadAllBytes(TestSetup.Parameters.LocalServerCertPath); + Assert.AreEqual( + expectedCertDer, + receivedCertDer, + "The certificate received in the callback should match the test server's certificate."); + } + } + + /// + /// Verifies that the connection validation callback is invoked and that returning CertificateRejected + /// causes the connection to fail. + /// + /// The task. + [Test] + public async Task ConnectionValidationCallback_RejectsConnection_ConnectFails() + { + var catalogRef = await this.AddRestCatalogAsync(); + + bool handlerCalled = false; + catalogRef.ConnectionValidationHandler = (args) => + { + handlerCalled = true; + return PackageCatalogConnectionValidationResult.CertificateRejected; + }; + + var connectResult = catalogRef.Connect(); + + Assert.IsNotNull(connectResult); + Assert.AreEqual(ConnectResultStatus.CatalogError, connectResult.Status, "Connect should fail when handler rejects the certificate."); + Assert.IsTrue(handlerCalled, "Handler should have been called."); + } + + /// + /// Verifies that setting ConnectionValidationHandler on the Microsoft Store catalog is allowed + /// when the BypassCertificatePinningForMicrosoftStore policy is enabled. + /// + [Test] + public void ConnectionValidationHandler_MicrosoftStore_PolicyEnabled_Allowed() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.Enable(); + + var catalogRef = this.packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.MicrosoftStore); + Assert.IsNotNull(catalogRef); + + // Policy enabled means the feature is allowed; setting the handler should not throw. + Assert.DoesNotThrow(() => + { + catalogRef.ConnectionValidationHandler = (args) => PackageCatalogConnectionValidationResult.Ok; + }); + } + + /// + /// Verifies that setting ConnectionValidationHandler on the Microsoft Store catalog is blocked + /// when the BypassCertificatePinningForMicrosoftStore policy is disabled. + /// + [Test] + public void ConnectionValidationHandler_MicrosoftStore_PolicyDisabled_ThrowsBlockedByPolicy() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.Disable(); + + var catalogRef = this.packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.MicrosoftStore); + Assert.IsNotNull(catalogRef); + + var ex = Assert.Throws(() => + { + catalogRef.ConnectionValidationHandler = (args) => PackageCatalogConnectionValidationResult.Ok; + }); + + Assert.AreEqual( + Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, + ex.HResult, + "Setting ConnectionValidationHandler on MicrosoftStore with policy disabled should return ERROR_BLOCKED_BY_POLICY."); + } + + /// + /// Verifies that setting ConnectionValidationHandler on a non-Store catalog is allowed + /// even when the BypassCertificatePinningForMicrosoftStore policy is configured. + /// + /// The task. + [Test] + public async Task ConnectionValidationHandler_NonStoreCatalog_PolicyEnabled_Allowed() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.Enable(); + + var catalogRef = await this.AddRestCatalogAsync(); + + // This should not throw — the policy only applies to the MicrosoftStore catalog. + Assert.DoesNotThrow(() => + { + catalogRef.ConnectionValidationHandler = (args) => PackageCatalogConnectionValidationResult.Ok; + }); + } + + /// + /// Verifies that setting ConnectionValidationHandler on a non-Store catalog is allowed + /// even when the BypassCertificatePinningForMicrosoftStore policy is disabled. + /// + /// The task. + [Test] + public async Task ConnectionValidationHandler_NonStoreCatalog_PolicyDisabled_Allowed() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.Disable(); + + var catalogRef = await this.AddRestCatalogAsync(); + + // The policy only applies to the MicrosoftStore catalog. + Assert.DoesNotThrow(() => + { + catalogRef.ConnectionValidationHandler = (args) => PackageCatalogConnectionValidationResult.Ok; + }); + } + + /// + /// Verifies that IsConnectionValidationHandlerEnabled returns true for a non-Store catalog + /// regardless of policy state. + /// + /// The task. + [Test] + public async Task IsConnectionValidationHandlerEnabled_NonStoreCatalog_ReturnsTrue() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.Disable(); + + var catalogRef = await this.AddRestCatalogAsync(); + Assert.IsTrue(catalogRef.IsConnectionValidationHandlerEnabled, "Non-store catalog should always report handler enabled."); + } + + /// + /// Verifies that IsConnectionValidationHandlerEnabled returns false for the MicrosoftStore + /// catalog when the policy is disabled. + /// + [Test] + public void IsConnectionValidationHandlerEnabled_MicrosoftStore_PolicyDisabled_ReturnsFalse() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.Disable(); + + var catalogRef = this.packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.MicrosoftStore); + Assert.IsNotNull(catalogRef); + Assert.IsFalse(catalogRef.IsConnectionValidationHandlerEnabled, "MicrosoftStore catalog with policy disabled should report handler not enabled."); + } + + /// + /// Verifies that IsConnectionValidationHandlerEnabled returns true for the MicrosoftStore + /// catalog when the policy is enabled. + /// + [Test] + public void IsConnectionValidationHandlerEnabled_MicrosoftStore_PolicyEnabled_ReturnsTrue() + { + GroupPolicyHelper.BypassCertificatePinningForMicrosoftStore.Enable(); + + var catalogRef = this.packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.MicrosoftStore); + Assert.IsNotNull(catalogRef); + Assert.IsTrue(catalogRef.IsConnectionValidationHandlerEnabled, "MicrosoftStore catalog with policy enabled should report handler enabled."); + } + + /// + /// Verifies that IsConnectionValidationHandlerEnabled returns true for the MicrosoftStore + /// catalog when the policy is not configured. + /// + [Test] + public void IsConnectionValidationHandlerEnabled_MicrosoftStore_PolicyNotConfigured_ReturnsTrue() + { + // Policy is left at NotConfigured (set by SetUp). + var catalogRef = this.packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.MicrosoftStore); + Assert.IsNotNull(catalogRef); + Assert.IsTrue(catalogRef.IsConnectionValidationHandlerEnabled, "MicrosoftStore catalog with policy not configured should report handler enabled."); + } + + private static byte[] GetCertificateDerBytes(Certificate certificate) + { + if (certificate is null) + { + return null; + } + + var blob = certificate.GetCertificateBlob(); + var bytes = new byte[blob.Length]; + using var reader = DataReader.FromBuffer(blob); + reader.ReadBytes(bytes); + return bytes; + } + + private async Task AddRestCatalogAsync() + { + var options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.Name = Constants.RestTestSourceName; + options.SourceUri = Constants.RestTestSourceUrl; + options.Type = Constants.RestTestSourceType; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + var addResult = await this.packageManager.AddPackageCatalogAsync(options); + Assert.IsNotNull(addResult); + Assert.AreEqual(AddPackageCatalogStatus.Ok, addResult.Status, $"Failed to add REST test source. Error: {addResult.ExtendedErrorCode?.HResult:X}"); + + var catalogRef = this.packageManager.GetPackageCatalogByName(Constants.RestTestSourceName); + Assert.IsNotNull(catalogRef, "REST test source catalog reference should not be null after adding."); + return catalogRef; + } + } + +#pragma warning disable SA1402 // File may only contain a single type + + /// + /// Verifies that the ConnectionValidationHandler setter is restricted to in-proc callers. + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class ConnectionValidationHandlerOutOfProcessInterop : BaseInterop + { + private PackageManager packageManager; + + /// + /// Initializes a new instance of the class. + /// + /// initializer. + public ConnectionValidationHandlerOutOfProcessInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + this.packageManager = this.TestFactory.CreatePackageManager(); + } + + /// + /// Verifies that setting ConnectionValidationHandler from an out-of-process caller throws an access denied exception. + /// + [Test] + public void ConnectionValidationHandler_OutOfProcess_ThrowsAccessDenied() + { + // Use the default winget catalog -- we just need any catalog reference. + var catalogRef = this.packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.OpenWindowsCatalog); + Assert.IsNotNull(catalogRef); + + // Setting the handler from out-of-proc should be rejected with E_ACCESSDENIED. + Assert.Throws(() => + { + catalogRef.ConnectionValidationHandler = (args) => PackageCatalogConnectionValidationResult.Ok; + }); + } + + /// + /// Verifies that IsConnectionValidationHandlerEnabled returns false for out-of-process callers. + /// + [Test] + public void IsConnectionValidationHandlerEnabled_OutOfProcess_ReturnsFalse() + { + var catalogRef = this.packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.OpenWindowsCatalog); + Assert.IsNotNull(catalogRef); + Assert.IsFalse(catalogRef.IsConnectionValidationHandlerEnabled, "Out-of-process callers should always see IsConnectionValidationHandlerEnabled as false."); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/TestRestSource/information b/src/AppInstallerCLIE2ETests/TestData/TestRestSource/information new file mode 100644 index 0000000000..b51107fca9 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/TestRestSource/information @@ -0,0 +1,6 @@ +{ + "Data": { + "SourceIdentifier": "TestRestSource", + "ServerSupportedVersions": ["1.0.0"] + } +} diff --git a/src/AppInstallerCommonCore/AdminSettings.cpp b/src/AppInstallerCommonCore/AdminSettings.cpp index ee692c3a9d..7ab9398171 100644 --- a/src/AppInstallerCommonCore/AdminSettings.cpp +++ b/src/AppInstallerCommonCore/AdminSettings.cpp @@ -277,12 +277,12 @@ namespace AppInstaller::Settings return GroupPolicies().GetState(policy); } - std::optional> GetPolicyStateForSetting(StringAdminSetting setting) + std::optional GetPolicyStateForSetting(StringAdminSetting setting) { switch (setting) { case AppInstaller::Settings::StringAdminSetting::DefaultProxy: - return GroupPolicies().GetValueRef(); + return GroupPolicies().GetValue(); default: return std::nullopt; } diff --git a/src/AppInstallerRepositoryCore/ISource.h b/src/AppInstallerRepositoryCore/ISource.h index 610129f8ef..9c511f7c1a 100644 --- a/src/AppInstallerRepositoryCore/ISource.h +++ b/src/AppInstallerRepositoryCore/ISource.h @@ -3,6 +3,7 @@ #pragma once #include "Public/winget/RepositorySource.h" #include +#include #include namespace AppInstaller::Repository @@ -92,6 +93,10 @@ namespace AppInstaller::Repository // Set authentication arguments. virtual void SetAuthenticationArguments(Authentication::AuthenticationArguments) {} + // Set a custom server certificate validation callback. + // Return true from the callback to accept the connection, false to reject. + virtual void SetServerCertificateValidationCallback(std::function) {} + // Determine if the source needs to be updated before being opened. virtual bool ShouldUpdateBeforeOpen(const std::optional&) { return false; } diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h index f26708c23b..53e2565474 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h @@ -293,6 +293,11 @@ namespace AppInstaller::Repository // Set authentication arguments. Must be set before Open to have effect. void SetAuthenticationArguments(Authentication::AuthenticationArguments args); + // Set a custom server certificate validation callback. Must be set before Open to have effect. + // Return true from the callback to accept the connection, false to reject. + // Only invoked when the certificate pinning group policy is not configured. + void SetServerCertificateValidationCallback(std::function callback); + // Set thread globals. Must be set before Open to have effect. void SetThreadGlobals(const std::shared_ptr& threadGlobals); @@ -304,7 +309,7 @@ namespace AppInstaller::Repository void InstalledPackageInformationOnly(bool value); // Determines if this source refers to the given well known source. - bool IsWellKnownSource(WellKnownSource wellKnownSource); + bool IsWellKnownSource(WellKnownSource wellKnownSource) const; // Execute a search on the source. SearchResult Search(const SearchRequest& request) const; diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index ba5569cd7d..dc4db9bbfe 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -687,6 +687,14 @@ namespace AppInstaller::Repository } } + void Source::SetServerCertificateValidationCallback(std::function callback) + { + for (auto& sourceReference : m_sourceReferences) + { + sourceReference->SetServerCertificateValidationCallback(callback); + } + } + void Source::SetThreadGlobals(const std::shared_ptr& threadGlobals) { for (auto& sourceReference : m_sourceReferences) @@ -705,7 +713,7 @@ namespace AppInstaller::Repository m_installedPackageInformationOnly = value; } - bool Source::IsWellKnownSource(WellKnownSource wellKnownSource) + bool Source::IsWellKnownSource(WellKnownSource wellKnownSource) const { SourceDetails details = GetDetails(); auto wellKnown = CheckForWellKnownSourceMatch(details.Name, details.Arg, details.Type); diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp index cac3fdf2d9..25555a9fd6 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp @@ -47,6 +47,13 @@ namespace AppInstaller::Repository::Rest m_authArgs = std::move(authArgs); } + void SetServerCertificateValidationCallback(std::function callback) override + { + Certificates::PinningConfiguration config; + config.AddChain(std::make_shared(std::move(callback))); + m_overridePinningConfiguration = std::move(config); + } + std::shared_ptr Open(IProgressCallback&) override { Initialize(); @@ -65,7 +72,10 @@ namespace AppInstaller::Repository::Rest std::call_once(m_initializeFlag, [&]() { - m_httpClientHelper.SetPinningConfiguration(m_details.CertificatePinningConfiguration, m_threadGlobals); + const auto& pinConfig = m_overridePinningConfiguration.has_value() + ? *m_overridePinningConfiguration + : m_details.CertificatePinningConfiguration; + m_httpClientHelper.SetPinningConfiguration(pinConfig, m_threadGlobals); m_restClientInformation = RestClient::GetInformation(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper); m_details.Identifier = m_restClientInformation.SourceIdentifier; @@ -92,6 +102,7 @@ namespace AppInstaller::Repository::Rest std::optional m_customHeader; std::string m_caller; Authentication::AuthenticationArguments m_authArgs; + std::optional m_overridePinningConfiguration; std::once_flag m_initializeFlag; std::shared_ptr m_threadGlobals; }; diff --git a/src/AppInstallerRepositoryCore/SourceList.cpp b/src/AppInstallerRepositoryCore/SourceList.cpp index 02682c0155..87c5c4e302 100644 --- a/src/AppInstallerRepositoryCore/SourceList.cpp +++ b/src/AppInstallerRepositoryCore/SourceList.cpp @@ -893,10 +893,10 @@ namespace AppInstaller::Repository if (GroupPolicies().GetState(TogglePolicy::Policy::AdditionalSources) == PolicyState::Enabled) { AICLI_LOG(Repo, Verbose, << "Additional sources GP is enabled..."); - auto additionalSourcesOpt = GroupPolicies().GetValueRef(); + auto additionalSourcesOpt = GroupPolicies().GetValue(); if (additionalSourcesOpt.has_value()) { - const auto& additionalSources = additionalSourcesOpt->get(); + const auto& additionalSources = additionalSourcesOpt.value(); for (const auto& additionalSource : additionalSources) { AICLI_LOG(Repo, Verbose, << "... with configured source " << additionalSource.Name); diff --git a/src/AppInstallerRepositoryCore/SourcePolicy.cpp b/src/AppInstallerRepositoryCore/SourcePolicy.cpp index 7662d306fa..e3e71d51fa 100644 --- a/src/AppInstallerRepositoryCore/SourcePolicy.cpp +++ b/src/AppInstallerRepositoryCore/SourcePolicy.cpp @@ -40,13 +40,13 @@ namespace AppInstaller::Repository template std::optional FindSourceInPolicy(std::string_view name, std::string_view type, std::string_view arg) { - auto sourcesOpt = GroupPolicies().GetValueRef

(); + auto sourcesOpt = GroupPolicies().GetValue

(); if (!sourcesOpt.has_value()) { return std::nullopt; } - const auto& sources = sourcesOpt->get(); + const auto& sources = sourcesOpt.value(); auto source = std::find_if( sources.begin(), sources.end(), diff --git a/src/AppInstallerSharedLib/Certificates.cpp b/src/AppInstallerSharedLib/Certificates.cpp index 8ba7a77a58..9f25e3534c 100644 --- a/src/AppInstallerSharedLib/Certificates.cpp +++ b/src/AppInstallerSharedLib/Certificates.cpp @@ -646,6 +646,26 @@ namespace AppInstaller::Certificates return result; } + CallbackPinningChainValidation::CallbackPinningChainValidation(std::function callback) + : m_callback(std::move(callback)) + { + } + + bool CallbackPinningChainValidation::Validate(PCCERT_CHAIN_CONTEXT chainContext) const + { + THROW_HR_IF(E_INVALIDARG, !chainContext || chainContext->cChain == 0); + PCCERT_SIMPLE_CHAIN simpleChain = chainContext->rgpChain[0]; + THROW_HR_IF(E_INVALIDARG, !simpleChain || simpleChain->cElement == 0); + PCCERT_CHAIN_ELEMENT leafElement = simpleChain->rgpElement[0]; + THROW_HR_IF(E_INVALIDARG, !leafElement || !leafElement->pCertContext); + return m_callback(leafElement->pCertContext); + } + + std::string CallbackPinningChainValidation::GetDescription() const + { + return ""; + } + PinningConfiguration::PinningConfiguration(std::string identifier) : m_identifier(identifier) { if (m_identifier.empty()) @@ -660,7 +680,12 @@ namespace AppInstaller::Certificates void PinningConfiguration::AddChain(PinningChain chain) { - AICLI_LOG(Core, Verbose, << "Adding chain to pinning configuration [" << m_identifier << "]:\n" << chain.GetDescription()); + AddChain(std::make_shared(std::move(chain))); + } + + void PinningConfiguration::AddChain(std::shared_ptr chain) + { + AICLI_LOG(Core, Verbose, << "Adding chain to pinning configuration [" << m_identifier << "]:\n" << chain->GetDescription()); m_configuration.emplace_back(std::move(chain)); } @@ -701,9 +726,9 @@ namespace AppInstaller::Certificates for (const auto& chain : m_configuration) { - if (chain.Validate(chainContext.get())) + if (chain->Validate(chainContext.get())) { - AICLI_LOG(Core, Verbose, << "Certificate `" << GetSimpleDisplayName(certContext) << "` accepted by pinning configuration:\n" << chain.GetDescription()); + AICLI_LOG(Core, Verbose, << "Certificate `" << GetSimpleDisplayName(certContext) << "` accepted by pinning configuration:\n" << chain->GetDescription()); result = true; break; } @@ -711,7 +736,7 @@ namespace AppInstaller::Certificates if (result) { - // Only cache a successful validation + // Only cache a successful validation. m_cachedCertificate.assign(encodedBegin, encodedEnd); } else @@ -784,7 +809,7 @@ namespace AppInstaller::Certificates for (const auto& chain : m_configuration) { - result = std::max(result, chain.GetRemainingLifetimePercentage()); + result = std::max(result, chain->GetRemainingLifetimePercentage()); } return result; diff --git a/src/AppInstallerSharedLib/GroupPolicy.cpp b/src/AppInstallerSharedLib/GroupPolicy.cpp index 5ce6d247bf..9bf7c63231 100644 --- a/src/AppInstallerSharedLib/GroupPolicy.cpp +++ b/src/AppInstallerSharedLib/GroupPolicy.cpp @@ -10,9 +10,9 @@ namespace AppInstaller::Settings { namespace { - const GroupPolicy& InstanceInternal(std::optional overridePolicy = {}) + GroupPolicy& InstanceInternal(std::optional overridePolicy = {}) { - const static GroupPolicy s_groupPolicy{ Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, "Software\\Policies\\Microsoft\\Windows\\AppInstaller") }; + static GroupPolicy s_groupPolicy{ Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, "Software\\Policies\\Microsoft\\Windows\\AppInstaller") }; static GroupPolicy* s_override = nullptr; if (overridePolicy.has_value()) @@ -385,19 +385,31 @@ namespace AppInstaller::Settings return Json::writeString(writerBuilder, json); } - GroupPolicy::GroupPolicy(const Registry::Key& key) + GroupPolicy::GroupPolicy(Registry::Key key) : m_key(std::move(key)) { - ValidateAllValuePolicies(key, m_values, std::make_index_sequence(ValuePolicy::Max)>()); + Reload(); + } + + void GroupPolicy::Reload() + { + std::map toggles; + ValuePoliciesMap values; + + ValidateAllValuePolicies(m_key, values, std::make_index_sequence(ValuePolicy::Max)>()); using Toggle_t = std::underlying_type_t; for (Toggle_t i = static_cast(TogglePolicy::Policy::None); i < static_cast(TogglePolicy::Policy::Max); ++i) { auto policy = static_cast(i); - m_toggles[policy] = GetStateInternal(key, policy); + toggles[policy] = GetStateInternal(m_key, policy); } + + auto lock = m_lock.lock_exclusive(); + m_toggles = std::move(toggles); + m_values = std::move(values); } - PolicyState GroupPolicy::GetState(TogglePolicy::Policy policy) const + PolicyState GroupPolicy::GetStateNoLock(TogglePolicy::Policy policy) const { auto itr = m_toggles.find(policy); if (itr == m_toggles.end()) @@ -408,6 +420,12 @@ namespace AppInstaller::Settings return itr->second; } + PolicyState GroupPolicy::GetState(TogglePolicy::Policy policy) const + { + auto lock = m_lock.lock_shared(); + return GetStateNoLock(policy); + } + bool GroupPolicy::IsEnabled(TogglePolicy::Policy policy) const { if (policy == TogglePolicy::Policy::None) @@ -415,7 +433,8 @@ namespace AppInstaller::Settings return true; } - PolicyState state = GetState(policy); + auto lock = m_lock.lock_shared(); + PolicyState state = GetStateNoLock(policy); if (state == PolicyState::NotConfigured) { return TogglePolicy::GetPolicy(policy).DefaultIsEnabled(); @@ -424,7 +443,7 @@ namespace AppInstaller::Settings return state == PolicyState::Enabled; } - GroupPolicy const& GroupPolicy::Instance() + GroupPolicy& GroupPolicy::Instance() { return InstanceInternal(); } diff --git a/src/AppInstallerSharedLib/Public/winget/Certificates.h b/src/AppInstallerSharedLib/Public/winget/Certificates.h index fb7f973bcc..c2034fa468 100644 --- a/src/AppInstallerSharedLib/Public/winget/Certificates.h +++ b/src/AppInstallerSharedLib/Public/winget/Certificates.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -115,8 +116,25 @@ namespace AppInstaller::Certificates PinningVerificationType m_pinning = PinningVerificationType::None; }; + // Interface for a single chain validation strategy within a PinningConfiguration. + // For a certificate to be accepted by PinningConfiguration, it must be accepted by at least one chain. + struct IPinningChainValidation + { + virtual ~IPinningChainValidation() = default; + + // Returns true if the certificate chain is accepted. + virtual bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const = 0; + + // Returns a human-readable description of this validation for logging. + virtual std::string GetDescription() const = 0; + + // Returns the remaining lifetime percentage of the pinned material (0.0 = expired, 1.0 = full life remaining). + // Implementations without a fixed lifetime should return 1.0. + virtual double GetRemainingLifetimePercentage() const = 0; + }; + // Contains the full chain of pinning details. - struct PinningChain + struct PinningChain : public IPinningChainValidation { PinningChain() = default; @@ -163,22 +181,36 @@ namespace AppInstaller::Certificates // Validates the given certificate chain against the configuration. // Returns true to indicate that the chain meets the pinning configuration criteria. // Returns false to indicate the it does not. - bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const; + bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const override; // Gets a description of the pinning chain. - std::string GetDescription() const; + std::string GetDescription() const override; // Loads the pinning chain from the given JSON. [[nodiscard]] bool LoadFrom(const Json::Value& configuration); // Determines how far the certificate chain is through its lifespan (the minimum of all of its certificates). - double GetRemainingLifetimePercentage() const; + double GetRemainingLifetimePercentage() const override; private: std::vector m_chain; bool m_partial = false; }; + // A chain validation that delegates to a caller-supplied callback. + struct CallbackPinningChainValidation : public IPinningChainValidation + { + // callback receives the leaf (end-entity) certificate and returns true to accept, false to reject. + explicit CallbackPinningChainValidation(std::function callback); + + bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const override; + std::string GetDescription() const override; + double GetRemainingLifetimePercentage() const override { return 1.0; } + + private: + std::function m_callback; + }; + // Holds the details about how a certificate chain is to be validated (aka "pinned"). struct PinningConfiguration { @@ -193,6 +225,7 @@ namespace AppInstaller::Certificates // Adds a possible chain to the configuration. // For a certificate to be valid, it must match only one of the configured chains. void AddChain(PinningChain chain); + void AddChain(std::shared_ptr chain); // Validates the given leaf certificate against the configuration. // Returns true to indicate that the certificate meets the pinning configuration criteria. @@ -213,7 +246,7 @@ namespace AppInstaller::Certificates std::string m_identifier; // The configured chains. - std::vector m_configuration; + std::vector> m_configuration; // We store the last certificate that was successfully validated to speed up subsequent checks. // Only cache a single certificate under the assumption that most of the time there will diff --git a/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h b/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h index bd8e4a844b..f4521a6d41 100644 --- a/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h +++ b/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -124,7 +125,6 @@ namespace AppInstaller::Settings { using value_t = std::monostate; using opt_value_t = std::nullopt_t; - using opt_ref_value_t = std::nullopt_t; static std::nullopt_t ReadAndValidate(const Registry::Key& policiesKey); }; @@ -134,7 +134,6 @@ namespace AppInstaller::Settings { \ using value_t = _type_; \ using opt_value_t = std::optional; \ - using opt_ref_value_t = std::optional>; \ static std::optional ReadAndValidate(const Registry::Key& policiesKey); \ _extra_ \ } @@ -165,9 +164,9 @@ namespace AppInstaller::Settings { using ValuePoliciesMap = EnumBasedVariantMap; - static GroupPolicy const& Instance(); + static GroupPolicy& Instance(); - GroupPolicy(const Registry::Key& key); + GroupPolicy(Registry::Key key); ~GroupPolicy() = default; GroupPolicy() = delete; @@ -185,6 +184,7 @@ namespace AppInstaller::Settings template typename details::ValuePolicyMapping

::opt_value_t GetValue() const { + auto lock = m_lock.lock_shared(); if (m_values.Contains(P)) { return m_values.Get

(); @@ -195,37 +195,21 @@ namespace AppInstaller::Settings } } - template - typename details::ValuePolicyMapping

::opt_ref_value_t GetValueRef() const - { - if (m_values.Contains(P)) - { - return std::cref(m_values.Get

()); - } - else - { - return std::nullopt; - } - } - template<> std::nullopt_t GetValue() const { return std::nullopt; } - template<> - std::nullopt_t GetValueRef() const - { - return std::nullopt; - } - PolicyState GetState(TogglePolicy::Policy policy) const; // Checks whether a policy is enabled, using an appropriate default when not configured. // Should not be used when not configured means something different than enabled/disabled. bool IsEnabled(TogglePolicy::Policy policy) const; + // Re-reads all policy values from the registry. + void Reload(); + #ifndef AICLI_DISABLE_TEST_HOOKS protected: static void OverrideInstance(GroupPolicy* gp); @@ -233,8 +217,13 @@ namespace AppInstaller::Settings #else private: #endif + Registry::Key m_key; + mutable wil::srwlock m_lock; std::map m_toggles; ValuePoliciesMap m_values; + + private: + PolicyState GetStateNoLock(TogglePolicy::Policy policy) const; }; inline const GroupPolicy& GroupPolicies() diff --git a/src/LocalhostWebServer/Startup.cs b/src/LocalhostWebServer/Startup.cs index 48f1fffce1..d71dc1f992 100644 --- a/src/LocalhostWebServer/Startup.cs +++ b/src/LocalhostWebServer/Startup.cs @@ -71,7 +71,18 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) RequestPath = StaticFileRequestPath, ContentTypeProvider = provider, ServeUnknownFileTypes = true, - DefaultContentType = "application/octet-stream" + DefaultContentType = "application/octet-stream", + OnPrepareResponse = ctx => + { + // REST source information files are extensionless JSON; set the correct content type + // and prevent caching so that each Connect() call gets a fresh response and + // triggers certificate validation. + if (ctx.File.Name == "information") + { + ctx.Context.Response.ContentType = "application/json"; + ctx.Context.Response.Headers.CacheControl = "no-store"; + } + }, }); app.UseDirectoryBrowser(new DirectoryBrowserOptions diff --git a/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj b/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj index 536d4a09a9..6fc74dcc20 100644 --- a/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj +++ b/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj @@ -16,8 +16,30 @@ 10.0.26100.0 - Microsoft.Management.Deployment;Windows.Foundation;Windows.System.ProcessorArchitecture - Windows.Foundation.Diagnostics + + Microsoft.Management.Deployment; + Windows.Data.Text.TextSegment; + Windows.Devices.Geolocation; + Windows.Foundation; + Windows.Globalization.DayOfWee; + Windows.Networking.Connectivity; + Windows.Networking.IDomainNameType; + Windows.Networking.DomainNameType; + Windows.Networking.IEndpointPair; + Windows.Networking.EndpointPair; + Windows.Networking.IHostName; + Windows.Networking.HostName; + Windows.Security.Cryptography.Certificates; + Windows.Storage; + Windows.Storage.Provider.FileUpdateStatus; + Windows.System.ProcessorArchitecture; + Windows.System.IUser; + Windows.System.User; + + + Windows.Foundation.Diagnostics; + Windows.Storage.Provider; + diff --git a/src/Microsoft.Management.Deployment/Helpers.cpp b/src/Microsoft.Management.Deployment/Helpers.cpp index 5975d5c268..8fa1462b60 100644 --- a/src/Microsoft.Management.Deployment/Helpers.cpp +++ b/src/Microsoft.Management.Deployment/Helpers.cpp @@ -59,6 +59,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation } } + bool IsInProcCaller() + { + auto [hr, callerProcessId] = GetCallerProcessId(); + return SUCCEEDED(hr) && callerProcessId == GetCurrentProcessId(); + } + std::wstring_view GetStringForCapability(Capability capability) { switch (capability) diff --git a/src/Microsoft.Management.Deployment/Helpers.h b/src/Microsoft.Management.Deployment/Helpers.h index 384dd3ad58..2df39ea7cc 100644 --- a/src/Microsoft.Management.Deployment/Helpers.h +++ b/src/Microsoft.Management.Deployment/Helpers.h @@ -17,6 +17,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation HRESULT EnsureProcessHasCapability(Capability requiredCapability, DWORD callerProcessId); HRESULT EnsureComCallerHasCapability(Capability requiredCapability); std::pair GetCallerProcessId(); + bool IsInProcCaller(); std::wstring TryGetCallerProcessInfo(DWORD callerProcessId); std::string GetCallerName(); bool IsBackgroundProcessForPolicy(); diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj index 86dbab6088..57899d710d 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj @@ -182,6 +182,7 @@ + @@ -234,6 +235,7 @@ + diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters index eef8b274ad..6c00ab8c5d 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters @@ -20,6 +20,7 @@ + @@ -70,6 +71,7 @@ + diff --git a/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp new file mode 100644 index 0000000000..b0eaf3dee7 --- /dev/null +++ b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageCatalogConnectionValidationEventArgs.h" +#include "PackageCatalogConnectionValidationEventArgs.g.cpp" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void PackageCatalogConnectionValidationEventArgs::Initialize(winrt::Windows::Security::Cryptography::Certificates::Certificate serverCertificate) + { + m_serverCertificate = serverCertificate; + } + + winrt::Windows::Security::Cryptography::Certificates::Certificate PackageCatalogConnectionValidationEventArgs::ServerCertificate() + { + return m_serverCertificate; + } +} diff --git a/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h new file mode 100644 index 0000000000..58798b2e3d --- /dev/null +++ b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageCatalogConnectionValidationEventArgs.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct PackageCatalogConnectionValidationEventArgs : PackageCatalogConnectionValidationEventArgsT + { + PackageCatalogConnectionValidationEventArgs() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(winrt::Windows::Security::Cryptography::Certificates::Certificate serverCertificate); +#endif + + winrt::Windows::Security::Cryptography::Certificates::Certificate ServerCertificate(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Windows::Security::Cryptography::Certificates::Certificate m_serverCertificate{ nullptr }; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp b/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp index a790a4c8ef..12ef4f333f 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp +++ b/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp @@ -6,6 +6,7 @@ #include "PackageCatalogReference.g.cpp" #include "PackageCatalogInfo.h" #include "PackageCatalog.h" +#include "PackageCatalogConnectionValidationEventArgs.h" #include "SourceAgreement.h" #include "ConnectResult.h" #include "AuthenticationInfo.h" @@ -33,6 +34,40 @@ namespace winrt::Microsoft::Management::Deployment::implementation updateResult->Initialize(status, terminationStatus); return *updateResult; } + + // Returns true if the ConnectionValidationHandler may be set for the given source. + // Returns false if the BypassCertificatePinningForMicrosoftStore policy is explicitly + // disabled and the source is the well-known MicrosoftStore catalog. + bool IsConnectionValidationHandlerEnabledForSource(const ::AppInstaller::Repository::Source& source) + { + using namespace AppInstaller::Settings; + if (source.IsWellKnownSource(::AppInstaller::Repository::WellKnownSource::MicrosoftStore)) + { + return GroupPolicies().GetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore) != PolicyState::Disabled; + } + + return true; + } + + std::function MakeServerCertificateValidationCallback( + winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler const& handler) + { + if (!handler) + { + return {}; + } + return [handler](PCCERT_CONTEXT certContext) -> bool + { + auto certBytes = winrt::array_view{ certContext->pbCertEncoded, certContext->pbCertEncoded + certContext->cbCertEncoded }; + auto buffer = winrt::Windows::Security::Cryptography::CryptographicBuffer::CreateFromByteArray(certBytes); + winrt::Windows::Security::Cryptography::Certificates::Certificate cert{ buffer }; + auto args = winrt::make_self>(); + args->Initialize(cert); + auto handlerResult = handler(*args); + AICLI_LOG(Repo, Info, << "PackageCatalogConnectionValidationHandler returned: " << handlerResult); + return handlerResult == winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationResult::Ok; + }; + } } void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::PackageCatalogInfo packageCatalogInfo, ::AppInstaller::Repository::Source sourceReference) @@ -117,6 +152,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation copy.SetCaller(callerName); copy.SetBackgroundUpdateInterval(catalog.PackageCatalogBackgroundUpdateInterval()); copy.InstalledPackageInformationOnly(catalog.InstalledPackageInformationOnly()); + auto validationCallback = MakeServerCertificateValidationCallback(catalogImpl->m_connectionValidationHandler); + if (validationCallback) + { + copy.SetServerCertificateValidationCallback(std::move(validationCallback)); + } if (catalog.AuthenticationInfo().AuthenticationType() != winrt::Microsoft::Management::Deployment::AuthenticationType::None) { copy.SetAuthenticationArguments(GetAuthenticationArguments(catalog.AuthenticationArguments())); @@ -164,6 +204,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation source.SetCaller(callerName); source.SetBackgroundUpdateInterval(PackageCatalogBackgroundUpdateInterval()); source.InstalledPackageInformationOnly(m_installedPackageInformationOnly); + auto validationCallback = MakeServerCertificateValidationCallback(m_connectionValidationHandler); + if (validationCallback) + { + source.SetServerCertificateValidationCallback(std::move(validationCallback)); + } if (AuthenticationInfo().AuthenticationType() != winrt::Microsoft::Management::Deployment::AuthenticationType::None) { source.SetAuthenticationArguments(GetAuthenticationArguments(m_authenticationArguments)); @@ -330,4 +375,22 @@ namespace winrt::Microsoft::Management::Deployment::implementation co_return GetRefreshPackageCatalogResult(terminationHR); } + + winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler PackageCatalogReference::ConnectionValidationHandler() + { + return m_connectionValidationHandler; + } + + void PackageCatalogReference::ConnectionValidationHandler(winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler const& value) + { + THROW_HR_IF(E_ACCESSDENIED, !IsInProcCaller()); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !IsConnectionValidationHandlerEnabledForSource(m_sourceReference)); + + m_connectionValidationHandler = value; + } + + bool PackageCatalogReference::IsConnectionValidationHandlerEnabled() + { + return IsInProcCaller() && IsConnectionValidationHandlerEnabledForSource(m_sourceReference); + } } diff --git a/src/Microsoft.Management.Deployment/PackageCatalogReference.h b/src/Microsoft.Management.Deployment/PackageCatalogReference.h index 75539d926a..1f25e96e1a 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogReference.h +++ b/src/Microsoft.Management.Deployment/PackageCatalogReference.h @@ -32,9 +32,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation void InstalledPackageInformationOnly(bool value); winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); - winrt::Microsoft::Management::Deployment::AuthenticationInfo AuthenticationInfo(); - // Contract 12.0 + winrt::Microsoft::Management::Deployment::AuthenticationInfo AuthenticationInfo(); + // Contract 12.0 winrt::Windows::Foundation::IAsyncOperationWithProgress RefreshPackageCatalogAsync(); + // Contract 29 + winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler ConnectionValidationHandler(); + void ConnectionValidationHandler(winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler const& value); + bool IsConnectionValidationHandlerEnabled(); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: @@ -50,6 +54,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; std::once_flag m_authenticationInfoOnceFlag; winrt::Microsoft::Management::Deployment::AuthenticationInfo m_authenticationInfo{ nullptr }; + winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler m_connectionValidationHandler{ nullptr }; #endif }; } diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 25567032f2..4a26b4ec12 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -859,6 +859,29 @@ namespace Microsoft.Management.Deployment MicrosoftEntraIdAuthenticationInfo MicrosoftEntraIdAuthenticationInfo { get; }; } + /// Result of a connection validation callback. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + enum PackageCatalogConnectionValidationResult + { + /// The connection was accepted. + Ok, + /// The connection was rejected because the certificate was not accepted. + CertificateRejected, + }; + + /// Arguments provided to a connection validation callback. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + runtimeclass PackageCatalogConnectionValidationEventArgs + { + /// The server certificate presented during the connection. + Windows.Security.Cryptography.Certificates.Certificate ServerCertificate { get; }; + } + + /// Callback invoked to validate a catalog connection. + /// Return Ok to accept the connection or another value to reject it for that reason. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + delegate PackageCatalogConnectionValidationResult PackageCatalogConnectionValidationHandler(PackageCatalogConnectionValidationEventArgs args); + /// Status of the Connect call [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] enum ConnectResultStatus @@ -967,6 +990,20 @@ namespace Microsoft.Management.Deployment /// The progress range is from 0 to 100. Windows.Foundation.IAsyncOperationWithProgress RefreshPackageCatalogAsync(); } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// A callback invoked to validate the server certificate during Connect or ConnectAsync. + /// Only available to in-process callers; out-of-process callers will receive E_ACCESSDENIED on set. + /// If the BypassCertificatePinningForMicrosoftStore group policy is disabled, this cannot be set + /// for the MicrosoftStore catalog; attempting to do so produces APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY. + PackageCatalogConnectionValidationHandler ConnectionValidationHandler; + + /// Indicates whether the ConnectionValidationHandler can be set for this catalog reference. + /// Returns false if setting the handler would be blocked by policy (e.g., the + /// BypassCertificatePinningForMicrosoftStore group policy is disabled for the MicrosoftStore catalog). + Boolean IsConnectionValidationHandlerEnabled { get; }; + } } /// Catalogs with PackageCatalogOrigin Predefined diff --git a/src/Microsoft.Management.Deployment/pch.h b/src/Microsoft.Management.Deployment/pch.h index bb44268681..b26798bcc1 100644 --- a/src/Microsoft.Management.Deployment/pch.h +++ b/src/Microsoft.Management.Deployment/pch.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include diff --git a/src/WindowsPackageManager/main.cpp b/src/WindowsPackageManager/main.cpp index 4ccb238d76..873c48b7a4 100644 --- a/src/WindowsPackageManager/main.cpp +++ b/src/WindowsPackageManager/main.cpp @@ -148,4 +148,13 @@ extern "C" return S_OK; } CATCH_RETURN(); + +#ifndef AICLI_DISABLE_TEST_HOOKS + __declspec(dllexport) WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerTestHook_ReloadGroupPolicy() try + { + AppInstaller::Settings::GroupPolicy::Instance().Reload(); + return S_OK; + } + CATCH_RETURN(); +#endif } From 2515a3568e8271bb0115ed0c6b62b5bcca899ad5 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 21 May 2026 08:25:34 -0700 Subject: [PATCH 04/18] Change symlink verification to avoid redirection guard policy (#6239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description Rather than comparing the symlink and desired target through `weakly_canonical`, this change checks that the symlink is pointing to the target via string comparison. This avoids "resolving" the symlink and the redirection guard policy. --- src/AppInstallerCLITests/Filesystem.cpp | 4 ++++ src/AppInstallerCLITests/main.cpp | 8 ++++++++ src/AppInstallerSharedLib/Filesystem.cpp | 17 +++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLITests/Filesystem.cpp b/src/AppInstallerCLITests/Filesystem.cpp index 69cf0b3e0f..733f38f08b 100644 --- a/src/AppInstallerCLITests/Filesystem.cpp +++ b/src/AppInstallerCLITests/Filesystem.cpp @@ -49,6 +49,10 @@ TEST_CASE("VerifySymlink", "[filesystem]") REQUIRE(VerifySymlink(symlinkPath, testFilePath)); REQUIRE_FALSE(VerifySymlink(symlinkPath, "badPath")); + // Verify case-insensitive comparison works (Windows paths are case-insensitive). + std::filesystem::path testFilePathUpperCase = basePath / "TESTFILE.TXT"; + REQUIRE(VerifySymlink(symlinkPath, testFilePathUpperCase)); + std::filesystem::remove(testFilePath); // Ensure that symlink existence does not check the target diff --git a/src/AppInstallerCLITests/main.cpp b/src/AppInstallerCLITests/main.cpp index 7c74746106..a91ca2ab11 100644 --- a/src/AppInstallerCLITests/main.cpp +++ b/src/AppInstallerCLITests/main.cpp @@ -67,6 +67,14 @@ int main(int argc, char** argv) { init_apartment(); + // Enable ProcessRedirectionTrustPolicy to match the MSIX packaged process context in which + // WinGet runs in production. This policy causes Windows to block traversal of user-created + // symlinks (e.g. via weakly_canonical), so enabling it here surfaces bugs that would only + // manifest in the real product environment. The policy is one-way; it cannot be reversed. + PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY redirectionPolicy{}; + redirectionPolicy.EnforceRedirectionTrust = 1; + SetProcessMitigationPolicy(ProcessRedirectionTrustPolicy, &redirectionPolicy, sizeof(redirectionPolicy)); + bool hasSetTestDataBasePath = false; bool waitBeforeReturn = false; bool keepSQLLogging = false; diff --git a/src/AppInstallerSharedLib/Filesystem.cpp b/src/AppInstallerSharedLib/Filesystem.cpp index c0bf01daf2..3c67411c80 100644 --- a/src/AppInstallerSharedLib/Filesystem.cpp +++ b/src/AppInstallerSharedLib/Filesystem.cpp @@ -477,8 +477,21 @@ namespace AppInstaller::Filesystem bool VerifySymlink(const std::filesystem::path& symlink, const std::filesystem::path& target) { - const std::filesystem::path& symlinkTargetPath = std::filesystem::weakly_canonical(symlink); - return symlinkTargetPath == std::filesystem::weakly_canonical(target); + // Use read_symlink to get the symlink's recorded target without traversing the filesystem + // chain. weakly_canonical would follow the symlink, which is blocked by + // ProcessRedirectionTrustPolicy (inherited from the MSIX packaged process context on + // newer Windows builds) when the symlink was created by a non-elevated process. + const std::filesystem::path symlinkTarget = std::filesystem::read_symlink(symlink); + + // If the recorded target is relative, resolve it against the symlink's parent directory. + const std::filesystem::path resolvedTarget = symlinkTarget.is_absolute() ? + symlinkTarget : (symlink.parent_path() / symlinkTarget); + + // Windows paths are case-insensitive. Use lexically_normal to resolve . and .. without + // filesystem access, then compare case-insensitively. + return Utility::ICUCaseInsensitiveEquals( + resolvedTarget.lexically_normal().u8string(), + target.lexically_normal().u8string()); } void AppendExtension(std::filesystem::path& target, const std::string& value) From 05d5fed59396af61651062e7d9b19936f4b86984 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 13:24:40 -0700 Subject: [PATCH 05/18] Bump uuid and @azure/msal-node in /tools/WinGetLogViewer (#6241) --- tools/WinGetLogViewer/package-lock.json | 31 ++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tools/WinGetLogViewer/package-lock.json b/tools/WinGetLogViewer/package-lock.json index bc84adb57c..547b6a72d5 100644 --- a/tools/WinGetLogViewer/package-lock.json +++ b/tools/WinGetLogViewer/package-lock.json @@ -190,20 +190,29 @@ } }, "node_modules/@azure/msal-node": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.2.tgz", - "integrity": "sha512-DoeSJ9U5KPAIZoHsPywvfEj2MhBniQe0+FSpjLUTdWoIkI999GB5USkW6nNEHnIaLVxROHXvprWA1KzdS1VQ4A==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.2.2.tgz", + "integrity": "sha512-toS+2AePxqyzb0YOKttDOOiSl3jrkK9aiqIvpurpis0O34QcIS5gToqrgT39p04Dpxw3YoUU0lxJKTpSFFfA6Q==", "dev": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "16.4.1", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" + "@azure/msal-common": "16.6.2", + "jsonwebtoken": "^9.0.0" }, "engines": { "node": ">=20" } }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.6.2.tgz", + "integrity": "sha512-hQjjsekAjB00cM1EmatWJlzhEoK2Qhz7Rj5gvM6tYf8iL7RM3tkxlpU9fG0+ofkulzg9AEEA6dIEnSmDr5ZqUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -3855,16 +3864,6 @@ "license": "MIT", "optional": true }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", From b3f5a9dbfe23d7d574caf1e68fba9399783ae97e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 07:53:22 -0700 Subject: [PATCH 06/18] Bump qs from 6.15.1 to 6.15.2 in /tools/WinGetLogViewer (#6246) --- tools/WinGetLogViewer/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/WinGetLogViewer/package-lock.json b/tools/WinGetLogViewer/package-lock.json index 547b6a72d5..89ad2f4b3f 100644 --- a/tools/WinGetLogViewer/package-lock.json +++ b/tools/WinGetLogViewer/package-lock.json @@ -2987,9 +2987,9 @@ } }, "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { From ab31639e4199d634364231567db1ead15ad7d757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Wed, 27 May 2026 11:46:42 -0700 Subject: [PATCH 07/18] Ensure portable command alias does not escape directory (#6251) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description This adds validation to ensure that a `PortableCommandAlias` does not use relative paths to point outside of its directory. It uses the same validation we already have for `RelativeFilePath` ## 🔗 References ## 🔍 Validation ## ✅ Checklist - [ ] Signed the [Contributor License Agreement](https://cla.opensource.microsoft.com) - [ ] Linked to an issue - [ ] Updated [Release Notes](../doc/ReleaseNotes.md) (if applicable) - [ ] Updated documentation (if applicable) - [ ] Updated [Copilot instructions](.github/copilot-instructions.md) (if build, architecture, or conventions changed) ## 📋 Issue Type - [ ] Bug fix - [ ] Feature - [ ] Task ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/6251) --- .../Workflows/ArchiveFlow.cpp | 2 +- .../AppInstallerCLITests.vcxproj | 3 ++ .../AppInstallerCLITests.vcxproj.filters | 3 ++ src/AppInstallerCLITests/Filesystem.cpp | 16 ++----- ...erTypeZip-InvalidPortableCommandAlias.yaml | 23 +++++++++ ...tallerTypeZip-InvalidRelativeFilePath.yaml | 4 +- src/AppInstallerCLITests/YamlManifest.cpp | 1 + .../Manifest/ManifestValidation.cpp | 48 ++++++++++++------- .../Public/winget/ManifestValidation.h | 1 + src/AppInstallerSharedLib/Filesystem.cpp | 9 ++-- .../Public/winget/Filesystem.h | 4 +- .../Common/ManifestErrorId.cs | 3 ++ 12 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidPortableCommandAlias.yaml diff --git a/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp b/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp index 680c0f1e15..9f2325323b 100644 --- a/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp @@ -92,7 +92,7 @@ namespace AppInstaller::CLI::Workflow { const std::filesystem::path& nestedInstallerPath = targetInstallerPath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath); - if (Filesystem::PathEscapesBaseDirectory(nestedInstallerPath, targetInstallerPath)) + if (Filesystem::PathEscapesBaseDirectory(nestedInstallerFile.RelativeFilePath)) { AICLI_LOG(CLI, Error, << "Path points to a location outside of the install directory: " << nestedInstallerPath); context.Reporter.Error() << Resource::String::InvalidPathToNestedInstaller << std::endl; diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index b8bc46728c..8dea7d9f4a 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -562,6 +562,9 @@ true + + true + true diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 61ae5cfea0..348f9cdfda 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -507,6 +507,9 @@ TestData + + TestData + TestData diff --git a/src/AppInstallerCLITests/Filesystem.cpp b/src/AppInstallerCLITests/Filesystem.cpp index 733f38f08b..59cf02b840 100644 --- a/src/AppInstallerCLITests/Filesystem.cpp +++ b/src/AppInstallerCLITests/Filesystem.cpp @@ -12,23 +12,15 @@ using namespace TestCommon; TEST_CASE("PathEscapesDirectory", "[filesystem]") { - TestCommon::TempDirectory tempDirectory("TempDirectory"); - const std::filesystem::path& basePath = tempDirectory.GetPath(); - std::string badRelativePath = "../../target.exe"; std::string badRelativePath2 = "test/../../target.exe"; std::string goodRelativePath = "target.exe"; std::string goodRelativePath2 = "test/../test1/target.exe"; - std::filesystem::path badPath = basePath / badRelativePath; - std::filesystem::path badPath2 = basePath / badRelativePath2; - std::filesystem::path goodPath = basePath / goodRelativePath; - std::filesystem::path goodPath2 = basePath / goodRelativePath2; - - REQUIRE(PathEscapesBaseDirectory(badPath, basePath)); - REQUIRE(PathEscapesBaseDirectory(badPath2, basePath)); - REQUIRE_FALSE(PathEscapesBaseDirectory(goodPath, basePath)); - REQUIRE_FALSE(PathEscapesBaseDirectory(goodPath2, basePath)); + REQUIRE(PathEscapesBaseDirectory(badRelativePath)); + REQUIRE(PathEscapesBaseDirectory(badRelativePath2)); + REQUIRE_FALSE(PathEscapesBaseDirectory(goodRelativePath)); + REQUIRE_FALSE(PathEscapesBaseDirectory(goodRelativePath2)); } TEST_CASE("VerifySymlink", "[filesystem]") diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidPortableCommandAlias.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidPortableCommandAlias.yaml new file mode 100644 index 0000000000..eeb5b9fd72 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidPortableCommandAlias.yaml @@ -0,0 +1,23 @@ +# Bad manifest. A nested installer file must not have a command alias outside the base directory. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip with a command alias outside the base directory +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath + PortableCommandAlias: ../command-alias +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml index d50d6c6fb9..3726d0aad0 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml @@ -1,4 +1,4 @@ -# Bad manifest. A nested installer file must have a RelativeFilePath specified. +# Bad manifest. A nested installer file must have a valid RelativeFilePath. # yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json PackageIdentifier: microsoft.msixsdk @@ -8,7 +8,7 @@ PackageName: AppInstaller Test Installer Publisher: Microsoft Corporation Moniker: AICLITestExe License: Test -ShortDescription: Test installer for zip without RelativeFilePath specified +ShortDescription: Test installer for zip with an invalid RelativeFilePath Scope: user Installers: - Architecture: x64 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 420dcd9d0d..df758fe857 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -882,6 +882,7 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-InstallerTypePortable-InvalidScope.yaml", "Scope is not supported for InstallerType portable." }, { "Manifest-Bad-InstallerTypeZip-DuplicateCommandAlias.yaml", "Duplicate portable command alias found." }, { "Manifest-Bad-InstallerTypeZip-DuplicateRelativeFilePath.yaml", "Duplicate relative file path found." }, + { "Manifest-Bad-InstallerTypeZip-InvalidPortableCommandAlias.yaml", "Portable command alias must not point to a location outside of base directory." }, { "Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml", "Relative file path must not point to a location outside of archive directory" }, { "Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml", "Required field missing. [RelativeFilePath]" }, { "Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml", "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes." }, diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index e4ddd190f8..b5c04a796b 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -77,6 +77,7 @@ namespace AppInstaller::Manifest { AppInstaller::Manifest::ManifestError::ArpVersionOverlapWithIndex, "DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index. Existing DisplayVersion range in index: "sv }, { AppInstaller::Manifest::ManifestError::ArpVersionValidationInternalError, "Internal error while validating DisplayVersion against index."sv }, { AppInstaller::Manifest::ManifestError::ExceededNestedInstallerFilesLimit, "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes."sv }, + { AppInstaller::Manifest::ManifestError::PortableCommandAliasEscapesDirectory, "Portable command alias must not point to a location outside of base directory."sv }, { AppInstaller::Manifest::ManifestError::RelativeFilePathEscapesDirectory, "Relative file path must not point to a location outside of archive directory."sv }, { AppInstaller::Manifest::ManifestError::ArpValidationError, "Arp Validation Error."sv }, { AppInstaller::Manifest::ManifestError::SchemaError, "Schema Error."sv }, @@ -349,9 +350,7 @@ namespace AppInstaller::Manifest } // Check that the relative file path does not escape base directory. - const std::filesystem::path& basePath = std::filesystem::current_path(); - const std::filesystem::path& fullPath = basePath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath); - if (AppInstaller::Filesystem::PathEscapesBaseDirectory(fullPath, basePath)) + if (AppInstaller::Filesystem::PathEscapesBaseDirectory(nestedInstallerFile.RelativeFilePath)) { resultErrors.emplace_back(ManifestError::RelativeFilePathEscapesDirectory, "RelativeFilePath"); } @@ -362,32 +361,45 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::DuplicateRelativeFilePath, "RelativeFilePath"); } - // Check for duplicate portable command alias values. - const auto& alias = Utility::ToLower(nestedInstallerFile.PortableCommandAlias); - if (!alias.empty() && !commandAliasSet.insert(alias).second) + if (!nestedInstallerFile.PortableCommandAlias.empty()) { - resultErrors.emplace_back(ManifestError::DuplicatePortableCommandAlias, "PortableCommandAlias"); - break; + // Check that the command alias does not escape the base directory. + if (AppInstaller::Filesystem::PathEscapesBaseDirectory(nestedInstallerFile.PortableCommandAlias)) + { + resultErrors.emplace_back(ManifestError::PortableCommandAliasEscapesDirectory, "PortableCommandAlias"); + } + + // Check for duplicate portable command alias values. + const auto& alias = Utility::ToLower(nestedInstallerFile.PortableCommandAlias); + if (!commandAliasSet.insert(alias).second) + { + resultErrors.emplace_back(ManifestError::DuplicatePortableCommandAlias, "PortableCommandAlias"); + break; + } } // If running full validation, check filetype if (options.FullValidation) { - const std::wstring lowerExtension = Utility::ToLower(fullPath.extension().wstring()); - - if (isPortable) + std::filesystem::path relativeFilePath{ Utility::ConvertToUTF16(nestedInstallerFile.RelativeFilePath) }; + if (relativeFilePath.has_extension()) { - if (fullPath.has_extension() && std::find(s_AllowedPortableFiletypes.begin(), s_AllowedPortableFiletypes.end(), lowerExtension) == s_AllowedPortableFiletypes.end()) + const std::wstring lowerExtension = Utility::ToLower(relativeFilePath.extension().wstring()); + + if (isPortable) { - resultErrors.emplace_back(ManifestError::InvalidPortableFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); + if (std::find(s_AllowedPortableFiletypes.begin(), s_AllowedPortableFiletypes.end(), lowerExtension) == s_AllowedPortableFiletypes.end()) + { + resultErrors.emplace_back(ManifestError::InvalidPortableFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); + } } - } - if (isFont) - { - if (fullPath.has_extension() && std::find(s_AllowedFontFiletypes.begin(), s_AllowedFontFiletypes.end(), lowerExtension) == s_AllowedFontFiletypes.end()) + if (isFont) { - resultErrors.emplace_back(ManifestError::InvalidFontFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); + if (std::find(s_AllowedFontFiletypes.begin(), s_AllowedFontFiletypes.end(), lowerExtension) == s_AllowedFontFiletypes.end()) + { + resultErrors.emplace_back(ManifestError::InvalidFontFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); + } } } } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h index c701df3b9c..55eef941ac 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h @@ -68,6 +68,7 @@ namespace AppInstaller::Manifest WINGET_DEFINE_RESOURCE_STRINGID(NoSuitableMinVersionDependency); WINGET_DEFINE_RESOURCE_STRINGID(NoSupportedPlatforms); WINGET_DEFINE_RESOURCE_STRINGID(OptionalFieldMissing); + WINGET_DEFINE_RESOURCE_STRINGID(PortableCommandAliasEscapesDirectory); WINGET_DEFINE_RESOURCE_STRINGID(RelativeFilePathEscapesDirectory); WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldEmpty); WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldMissing); diff --git a/src/AppInstallerSharedLib/Filesystem.cpp b/src/AppInstallerSharedLib/Filesystem.cpp index 3c67411c80..2e0e283359 100644 --- a/src/AppInstallerSharedLib/Filesystem.cpp +++ b/src/AppInstallerSharedLib/Filesystem.cpp @@ -370,12 +370,11 @@ namespace AppInstaller::Filesystem return (GetVolumeInformationFlags(path) & FILE_SUPPORTS_REPARSE_POINTS) != 0; } - bool PathEscapesBaseDirectory(const std::filesystem::path& target, const std::filesystem::path& base) + bool PathEscapesBaseDirectory(std::string_view relativePath) { - const auto& targetPath = std::filesystem::weakly_canonical(target); - const auto& basePath = std::filesystem::weakly_canonical(base); - auto [a, b] = std::mismatch(targetPath.begin(), targetPath.end(), basePath.begin(), basePath.end()); - return (b != basePath.end()); + // Normalize the path, then check if the first part is ".." + auto resolvedPath = std::filesystem::path{ relativePath }.lexically_normal(); + return !resolvedPath.empty() && *resolvedPath.begin() == ".."; } // Complicated rename algorithm due to somewhat arbitrary failures. diff --git a/src/AppInstallerSharedLib/Public/winget/Filesystem.h b/src/AppInstallerSharedLib/Public/winget/Filesystem.h index c833e8e921..1a7df6aa28 100644 --- a/src/AppInstallerSharedLib/Public/winget/Filesystem.h +++ b/src/AppInstallerSharedLib/Public/winget/Filesystem.h @@ -21,8 +21,8 @@ namespace AppInstaller::Filesystem // Checks if the file system at path support reparse points bool SupportsReparsePoints(const std::filesystem::path& path); - // Checks if the canonical form of the path points to a location outside of the provided base path. - bool PathEscapesBaseDirectory(const std::filesystem::path& target, const std::filesystem::path& base); + // Checks if a relative paths points to a location outside of the base path. + bool PathEscapesBaseDirectory(std::string_view relativePath); // Renames the file to a new path. void RenameFile(const std::filesystem::path& from, const std::filesystem::path& to); diff --git a/src/WinGetUtilInterop/Common/ManifestErrorId.cs b/src/WinGetUtilInterop/Common/ManifestErrorId.cs index 43456c613c..607bce3d2e 100644 --- a/src/WinGetUtilInterop/Common/ManifestErrorId.cs +++ b/src/WinGetUtilInterop/Common/ManifestErrorId.cs @@ -159,6 +159,9 @@ public enum ManifestErrorId ///

Optional field missing. OptionalFieldMissing, + /// Portable command alias must not point to a location outside of base directory. + PortableCommandAliasEscapesDirectory, + /// Relative file path must not point to a location outside of archive directory. RelativeFilePathEscapesDirectory, From e270ee3dfd41a5f9a5cbbc1f49e0fdf84dcb3220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Wed, 27 May 2026 11:47:10 -0700 Subject: [PATCH 08/18] Make sfs-client a vcpkg port (#6243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description Removes the git-subtree for sfs-client and replaces it with a local vcpkg port. This port is based on the [template provided by sfs-client](https://github.com/microsoft/sfs-client/tree/main/sfs-client-vcpkg-port/sfs-client) * Updated the script that creates a local overlay to fetch this port. * Added a small patch to fix a build error in the port. * Created a new folder for the custom port patches. This means duplicating the files, but prevents losing them when re-creating the ports. * Added a job to the build pipeline to ensure that the ports still match what the creation script produces, to ensure they're kept in sync. This fixes a CG alert for c-ares triggered by having sfs-client's cgmanifest checked in, which caused CG to consider that as the version used despite us updating the dependency. ## 🔗 References ## 🔍 Validation It builds :D ## ✅ Checklist - [ ] Signed the [Contributor License Agreement](https://cla.opensource.microsoft.com) - [ ] Linked to an issue - [ ] Updated [Release Notes](../doc/ReleaseNotes.md) (if applicable) - [ ] Updated documentation (if applicable) - [ ] Updated [Copilot instructions](.github/copilot-instructions.md) (if build, architecture, or conventions changed) ## 📋 Issue Type - [ ] Bug fix - [ ] Feature - [ ] Task ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/6243) --- cgmanifest.json | 2 +- src/AppInstallerCLI.sln | 31 +- .../AppInstallerCLITests.vcxproj | 15 +- .../AppInstallerCommonCore.vcxproj | 18 +- src/SfsClient/SfsClient.vcxproj | 291 -------- src/SfsClient/SfsClient.vcxproj.filters | 159 ---- src/SfsClient/readme.md | 19 - src/SfsClient/sfs-client/.clang-format | 29 - src/SfsClient/sfs-client/.cmake-format.json | 7 - src/SfsClient/sfs-client/.github/CODEOWNERS | 2 - .../.github/ISSUE_TEMPLATE/bug_report.md | 24 - .../.github/ISSUE_TEMPLATE/feature_request.md | 20 - .../sfs-client/.github/dependabot.yml | 23 - .../.github/pull_request_template.md | 15 - .../workflows/initialize-codeql/action.yml | 12 - .../workflows/install-winget/action.yml | 56 -- .../.github/workflows/main-build-ubuntu.yml | 46 -- .../.github/workflows/main-build-windows.yml | 53 -- .../sfs-client/.github/workflows/pr.yml | 87 --- src/SfsClient/sfs-client/.gitignore | 45 -- src/SfsClient/sfs-client/API.md | 59 -- src/SfsClient/sfs-client/CMakeLists.txt | 79 -- src/SfsClient/sfs-client/CODE_OF_CONDUCT.md | 9 - src/SfsClient/sfs-client/DEVELOPMENT.md | 6 - src/SfsClient/sfs-client/LICENSE | 21 - src/SfsClient/sfs-client/NOTICE.md | 441 ----------- src/SfsClient/sfs-client/README.md | 195 ----- src/SfsClient/sfs-client/SECURITY.md | 41 - src/SfsClient/sfs-client/SUPPORT.md | 11 - src/SfsClient/sfs-client/TEST.md | 12 - src/SfsClient/sfs-client/cgmanifest.json | 85 --- .../sfs-client/client/CMakeLists.txt | 147 ---- .../client/include/sfsclient/AppContent.h | 87 --- .../client/include/sfsclient/AppFile.h | 59 -- .../include/sfsclient/ApplicabilityDetails.h | 42 -- .../client/include/sfsclient/ClientConfig.h | 34 - .../client/include/sfsclient/Content.h | 62 -- .../client/include/sfsclient/ContentId.h | 48 -- .../client/include/sfsclient/File.h | 70 -- .../client/include/sfsclient/Logging.h | 33 - .../client/include/sfsclient/RequestParams.h | 46 -- .../client/include/sfsclient/Result.h | 89 --- .../client/include/sfsclient/SFSClient.h | 80 -- .../sfs-client/client/src/AppContent.cpp | 90 --- .../sfs-client/client/src/AppFile.cpp | 60 -- .../client/src/ApplicabilityDetails.cpp | 35 - .../sfs-client/client/src/Content.cpp | 91 --- .../sfs-client/client/src/ContentId.cpp | 49 -- src/SfsClient/sfs-client/client/src/File.cpp | 68 -- .../sfs-client/client/src/Logging.cpp | 22 - .../sfs-client/client/src/Result.cpp | 127 ---- .../sfs-client/client/src/SFSClient.cpp | 57 -- .../client/src/details/ContentUtil.cpp | 117 --- .../client/src/details/ContentUtil.h | 61 -- .../client/src/details/CorrelationVector.cpp | 55 -- .../client/src/details/CorrelationVector.h | 40 - .../sfs-client/client/src/details/Env.cpp | 84 --- .../sfs-client/client/src/details/Env.h | 40 - .../client/src/details/ErrorHandling.cpp | 75 -- .../client/src/details/ErrorHandling.h | 87 --- .../sfs-client/client/src/details/OSInfo.cpp | 84 --- .../sfs-client/client/src/details/OSInfo.h | 12 - .../client/src/details/ReportingHandler.cpp | 28 - .../client/src/details/ReportingHandler.h | 80 -- .../client/src/details/SFSClientImpl.cpp | 415 ----------- .../client/src/details/SFSClientImpl.h | 114 --- .../client/src/details/SFSClientInterface.h | 104 --- .../client/src/details/SFSException.cpp | 28 - .../client/src/details/SFSException.h | 28 - .../client/src/details/SFSUrlBuilder.cpp | 81 -- .../client/src/details/SFSUrlBuilder.h | 47 -- .../client/src/details/TestOverride.cpp | 59 -- .../client/src/details/TestOverride.h | 52 -- .../client/src/details/UrlBuilder.cpp | 192 ----- .../client/src/details/UrlBuilder.h | 144 ---- .../sfs-client/client/src/details/Util.cpp | 21 - .../sfs-client/client/src/details/Util.h | 12 - .../src/details/connection/Connection.cpp | 21 - .../src/details/connection/Connection.h | 57 -- .../details/connection/ConnectionConfig.cpp | 16 - .../src/details/connection/ConnectionConfig.h | 30 - .../details/connection/ConnectionManager.cpp | 16 - .../details/connection/ConnectionManager.h | 28 - .../src/details/connection/CurlConnection.cpp | 432 ----------- .../src/details/connection/CurlConnection.h | 64 -- .../connection/CurlConnectionManager.cpp | 53 -- .../connection/CurlConnectionManager.h | 24 - .../src/details/connection/HttpHeader.cpp | 38 - .../src/details/connection/HttpHeader.h | 21 - .../connection/mock/MockConnection.cpp | 26 - .../details/connection/mock/MockConnection.h | 23 - .../connection/mock/MockConnectionManager.cpp | 21 - .../connection/mock/MockConnectionManager.h | 24 - .../client/src/details/entity/ContentType.cpp | 19 - .../client/src/details/entity/ContentType.h | 17 - .../client/src/details/entity/FileEntity.cpp | 289 -------- .../client/src/details/entity/FileEntity.h | 71 -- .../src/details/entity/VersionEntity.cpp | 153 ---- .../client/src/details/entity/VersionEntity.h | 61 -- .../sfs-client/client/tests/CMakeLists.txt | 64 -- .../tests/functional/SFSClientTests.cpp | 461 ------------ .../details/CurlConnectionTests.cpp | 611 --------------- .../functional/details/SFSClientImplTests.cpp | 358 --------- .../client/tests/mock/MockWebServer.cpp | 698 ------------------ .../client/tests/mock/MockWebServer.h | 68 -- .../client/tests/mock/ProxyServer.cpp | 97 --- .../client/tests/mock/ProxyServer.h | 34 - .../client/tests/mock/ServerCommon.cpp | 121 --- .../client/tests/mock/ServerCommon.h | 84 --- .../client/tests/unit/AppContentTests.cpp | 215 ------ .../client/tests/unit/AppFileTests.cpp | 117 --- .../tests/unit/ApplicabilityDetailsTests.cpp | 61 -- .../client/tests/unit/ContentIdTests.cpp | 69 -- .../client/tests/unit/ContentTests.cpp | 160 ---- .../client/tests/unit/FileTests.cpp | 72 -- .../client/tests/unit/ResultTests.cpp | 58 -- .../client/tests/unit/SFSClientTests.cpp | 338 --------- .../details/CurlConnectionManagerTests.cpp | 48 -- .../unit/details/CurlConnectionTests.cpp | 106 --- .../client/tests/unit/details/EnvTests.cpp | 119 --- .../tests/unit/details/ErrorHandlingTests.cpp | 255 ------- .../unit/details/ReportingHandlerTests.cpp | 190 ----- .../tests/unit/details/SFSClientImplTests.cpp | 410 ---------- .../tests/unit/details/SFSUrlBuilderTests.cpp | 100 --- .../tests/unit/details/TestOverrideTests.cpp | 131 ---- .../tests/unit/details/UrlBuilderTests.cpp | 143 ---- .../client/tests/unit/details/UtilTests.cpp | 32 - .../unit/details/entity/FileEntityTests.cpp | 639 ---------------- .../details/entity/VersionEntityTests.cpp | 305 -------- .../client/tests/util/SFSExceptionMatcher.cpp | 59 -- .../client/tests/util/SFSExceptionMatcher.h | 51 -- .../client/tests/util/TestHelper.cpp | 49 -- .../sfs-client/client/tests/util/TestHelper.h | 26 - .../sfs-client/cmake/SFSOptions.cmake | 15 - .../cmake/sfsclient-config.cmake.in | 16 - .../sfs-client/pre-commit-wrapper.sh | 18 - src/SfsClient/sfs-client/pre-commit.sh | 152 ---- .../sfs-client/samples/CMakeLists.txt | 5 - src/SfsClient/sfs-client/samples/README.md | 9 - .../integration-do-client/CMakeLists.txt | 62 -- .../IntegrationDOClient.cpp | 333 --------- .../sfs-client/samples/tool/CMakeLists.txt | 21 - .../sfs-client/samples/tool/SFSClientTool.cpp | 548 -------------- src/SfsClient/sfs-client/scripts/Build.ps1 | 90 --- src/SfsClient/sfs-client/scripts/Setup.ps1 | 169 ----- src/SfsClient/sfs-client/scripts/Test.ps1 | 28 - src/SfsClient/sfs-client/scripts/build.sh | 163 ---- .../sfs-client/scripts/check-format.py | 66 -- .../sfs-client/scripts/pip.requirements.txt | 2 - src/SfsClient/sfs-client/scripts/setup.sh | 190 ----- src/SfsClient/sfs-client/scripts/test.sh | 60 -- .../sfs-client/portfile.cmake | 37 - .../sfs-client-vcpkg-port/sfs-client/usage | 4 - .../sfs-client/vcpkg.json | 30 - .../x64-windows-static-custom.cmake | 6 - src/SfsClient/sfs-client/vcpkg.json | 64 -- src/VcpkgPortOverlay/.gitignore | 6 + src/VcpkgPortOverlay/CreatePortOverlay.ps1 | 271 ++++++- src/VcpkgPortOverlay/README.md | 7 + .../add-server-certificate-validation.patch | 169 ----- .../cpprestsdk/fix-find-openssl.patch | 18 - src/VcpkgPortOverlay/cpprestsdk/fix-uwp.patch | 28 - .../cpprestsdk/fix_narrowing.patch | 50 -- .../cpprestsdk/portfile.cmake | 61 -- src/VcpkgPortOverlay/cpprestsdk/test.patch | 23 - src/VcpkgPortOverlay/cpprestsdk/vcpkg.json | 117 --- .../detours/find-jmp-bounds-arm64.patch | 24 - src/VcpkgPortOverlay/detours/portfile.cmake | 34 - src/VcpkgPortOverlay/detours/usage | 7 - src/VcpkgPortOverlay/detours/vcpkg.json | 9 - .../libyaml/export-pkgconfig.patch | 16 - .../libyaml/fix-POSIX_name.patch | 13 - src/VcpkgPortOverlay/libyaml/portfile.cmake | 33 - src/VcpkgPortOverlay/libyaml/vcpkg.json | 17 - .../add-server-certificate-validation.patch | 69 ++ ...ove-unconditional-toolchain-override.patch | 12 + .../WindowsPackageManager.vcxproj | 3 - src/vcpkg.json | 1 + src/vcpkg.props | 7 + 179 files changed, 384 insertions(+), 15921 deletions(-) delete mode 100644 src/SfsClient/SfsClient.vcxproj delete mode 100644 src/SfsClient/SfsClient.vcxproj.filters delete mode 100644 src/SfsClient/readme.md delete mode 100644 src/SfsClient/sfs-client/.clang-format delete mode 100644 src/SfsClient/sfs-client/.cmake-format.json delete mode 100644 src/SfsClient/sfs-client/.github/CODEOWNERS delete mode 100644 src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 src/SfsClient/sfs-client/.github/dependabot.yml delete mode 100644 src/SfsClient/sfs-client/.github/pull_request_template.md delete mode 100644 src/SfsClient/sfs-client/.github/workflows/initialize-codeql/action.yml delete mode 100644 src/SfsClient/sfs-client/.github/workflows/install-winget/action.yml delete mode 100644 src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml delete mode 100644 src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml delete mode 100644 src/SfsClient/sfs-client/.github/workflows/pr.yml delete mode 100644 src/SfsClient/sfs-client/.gitignore delete mode 100644 src/SfsClient/sfs-client/API.md delete mode 100644 src/SfsClient/sfs-client/CMakeLists.txt delete mode 100644 src/SfsClient/sfs-client/CODE_OF_CONDUCT.md delete mode 100644 src/SfsClient/sfs-client/DEVELOPMENT.md delete mode 100644 src/SfsClient/sfs-client/LICENSE delete mode 100644 src/SfsClient/sfs-client/NOTICE.md delete mode 100644 src/SfsClient/sfs-client/README.md delete mode 100644 src/SfsClient/sfs-client/SECURITY.md delete mode 100644 src/SfsClient/sfs-client/SUPPORT.md delete mode 100644 src/SfsClient/sfs-client/TEST.md delete mode 100644 src/SfsClient/sfs-client/cgmanifest.json delete mode 100644 src/SfsClient/sfs-client/client/CMakeLists.txt delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/AppContent.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/AppFile.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/ApplicabilityDetails.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/ClientConfig.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/Content.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/ContentId.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/File.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/Logging.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/Result.h delete mode 100644 src/SfsClient/sfs-client/client/include/sfsclient/SFSClient.h delete mode 100644 src/SfsClient/sfs-client/client/src/AppContent.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/AppFile.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/ApplicabilityDetails.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/Content.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/ContentId.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/File.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/Logging.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/Result.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/SFSClient.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/ContentUtil.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/ContentUtil.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/CorrelationVector.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/CorrelationVector.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/Env.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/Env.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/ErrorHandling.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/ErrorHandling.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/OSInfo.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/OSInfo.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/ReportingHandler.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/ReportingHandler.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/SFSClientImpl.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/SFSClientImpl.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/SFSClientInterface.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/SFSException.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/SFSException.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/TestOverride.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/TestOverride.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/UrlBuilder.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/Util.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/Util.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/Connection.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/Connection.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/entity/ContentType.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/entity/FileEntity.h delete mode 100644 src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp delete mode 100644 src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.h delete mode 100644 src/SfsClient/sfs-client/client/tests/CMakeLists.txt delete mode 100644 src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/functional/details/SFSClientImplTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/mock/MockWebServer.h delete mode 100644 src/SfsClient/sfs-client/client/tests/mock/ProxyServer.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/mock/ProxyServer.h delete mode 100644 src/SfsClient/sfs-client/client/tests/mock/ServerCommon.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/mock/ServerCommon.h delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/AppContentTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/AppFileTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/ApplicabilityDetailsTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/ContentIdTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/ContentTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/FileTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/ResultTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionManagerTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/EnvTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/ErrorHandlingTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/ReportingHandlerTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/SFSClientImplTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/SFSUrlBuilderTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/TestOverrideTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/UrlBuilderTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/UtilTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/entity/FileEntityTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/unit/details/entity/VersionEntityTests.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.h delete mode 100644 src/SfsClient/sfs-client/client/tests/util/TestHelper.cpp delete mode 100644 src/SfsClient/sfs-client/client/tests/util/TestHelper.h delete mode 100644 src/SfsClient/sfs-client/cmake/SFSOptions.cmake delete mode 100644 src/SfsClient/sfs-client/cmake/sfsclient-config.cmake.in delete mode 100644 src/SfsClient/sfs-client/pre-commit-wrapper.sh delete mode 100644 src/SfsClient/sfs-client/pre-commit.sh delete mode 100644 src/SfsClient/sfs-client/samples/CMakeLists.txt delete mode 100644 src/SfsClient/sfs-client/samples/README.md delete mode 100644 src/SfsClient/sfs-client/samples/integration-do-client/CMakeLists.txt delete mode 100644 src/SfsClient/sfs-client/samples/integration-do-client/IntegrationDOClient.cpp delete mode 100644 src/SfsClient/sfs-client/samples/tool/CMakeLists.txt delete mode 100644 src/SfsClient/sfs-client/samples/tool/SFSClientTool.cpp delete mode 100644 src/SfsClient/sfs-client/scripts/Build.ps1 delete mode 100644 src/SfsClient/sfs-client/scripts/Setup.ps1 delete mode 100644 src/SfsClient/sfs-client/scripts/Test.ps1 delete mode 100755 src/SfsClient/sfs-client/scripts/build.sh delete mode 100644 src/SfsClient/sfs-client/scripts/check-format.py delete mode 100644 src/SfsClient/sfs-client/scripts/pip.requirements.txt delete mode 100755 src/SfsClient/sfs-client/scripts/setup.sh delete mode 100755 src/SfsClient/sfs-client/scripts/test.sh delete mode 100644 src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/portfile.cmake delete mode 100644 src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/usage delete mode 100644 src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/vcpkg.json delete mode 100644 src/SfsClient/sfs-client/vcpkg-custom-triplets/x64-windows-static-custom.cmake delete mode 100644 src/SfsClient/sfs-client/vcpkg.json create mode 100644 src/VcpkgPortOverlay/.gitignore delete mode 100644 src/VcpkgPortOverlay/cpprestsdk/add-server-certificate-validation.patch delete mode 100644 src/VcpkgPortOverlay/cpprestsdk/fix-find-openssl.patch delete mode 100644 src/VcpkgPortOverlay/cpprestsdk/fix-uwp.patch delete mode 100644 src/VcpkgPortOverlay/cpprestsdk/fix_narrowing.patch delete mode 100644 src/VcpkgPortOverlay/cpprestsdk/portfile.cmake delete mode 100644 src/VcpkgPortOverlay/cpprestsdk/test.patch delete mode 100644 src/VcpkgPortOverlay/cpprestsdk/vcpkg.json delete mode 100644 src/VcpkgPortOverlay/detours/find-jmp-bounds-arm64.patch delete mode 100644 src/VcpkgPortOverlay/detours/portfile.cmake delete mode 100644 src/VcpkgPortOverlay/detours/usage delete mode 100644 src/VcpkgPortOverlay/detours/vcpkg.json delete mode 100644 src/VcpkgPortOverlay/libyaml/export-pkgconfig.patch delete mode 100644 src/VcpkgPortOverlay/libyaml/fix-POSIX_name.patch delete mode 100644 src/VcpkgPortOverlay/libyaml/portfile.cmake delete mode 100644 src/VcpkgPortOverlay/libyaml/vcpkg.json create mode 100644 src/VcpkgPortOverlay/patches/cpprestsdk/add-server-certificate-validation.patch create mode 100644 src/VcpkgPortOverlay/patches/sfs-client/remove-unconditional-toolchain-override.patch diff --git a/cgmanifest.json b/cgmanifest.json index 0ab008bb1a..fc5c3c3447 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -33,7 +33,7 @@ "type": "git", "git": { "repositoryUrl": "https://github.com/microsoft/sfs-client.git", - "commitHash": "ff315ecfa2ef2953d8a808e51e8a61a4e0759180" + "commitHash": "0e27525d597c730e71646fd0b15bdc8c8503f24d" } } }, diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 3a37b38505..96505d4af5 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -180,8 +180,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5A52D9FC PowerShell\tests\RunTests.ps1 = PowerShell\tests\RunTests.ps1 EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SfsClient", "SfsClient\SfsClient.vcxproj", "{1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgCustomTriplets", "VcpkgCustomTriplets", "{76B26B2C-602A-4AD0-9736-4162D3FCA92A}" ProjectSection(SolutionItems) = preProject VcpkgCustomTriplets\arm64-release-static.cmake = VcpkgCustomTriplets\arm64-release-static.cmake @@ -199,6 +197,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgCustomTriplets", "Vcpk VcpkgCustomTriplets\x86.cmake = VcpkgCustomTriplets\x86.cmake EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgPortOverlay", "VcpkgPortOverlay", "{EF18BED6-EBC0-45E9-8D61-4202528E8AAB}" + ProjectSection(SolutionItems) = preProject + VcpkgPortOverlay\CreatePortOverlay.ps1 = VcpkgPortOverlay\CreatePortOverlay.ps1 + VcpkgPortOverlay\README.md = VcpkgPortOverlay\README.md + EndProjectSection +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment.OutOfProc", "Microsoft.Management.Deployment.OutOfProc\Microsoft.Management.Deployment.OutOfProc.vcxproj", "{0BA531C8-CF0C-405B-8221-0FE51BA529D1}" ProjectSection(ProjectDependencies) = postProject {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} @@ -941,27 +945,6 @@ Global {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Debug|ARM64.Build.0 = Debug|ARM64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Debug|x64.ActiveCfg = Debug|x64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Debug|x64.Build.0 = Debug|x64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Debug|x86.ActiveCfg = Debug|Win32 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Debug|x86.Build.0 = Debug|Win32 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Fuzzing|x64.ActiveCfg = Release|x64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Fuzzing|x86.ActiveCfg = Release|Win32 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Release|ARM64.ActiveCfg = Release|ARM64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Release|ARM64.Build.0 = Release|ARM64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Release|x64.ActiveCfg = Release|x64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Release|x64.Build.0 = Release|x64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Release|x86.ActiveCfg = Release|Win32 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.Release|x86.Build.0 = Release|Win32 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.ActiveCfg = Debug|ARM64 {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.Build.0 = Debug|ARM64 {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x64.ActiveCfg = Debug|x64 @@ -1109,8 +1092,8 @@ Global {C54F80ED-B736-49B0-9BD3-662F57024D01} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} {272B2B0E-40D4-4F0F-B187-519A6EF89B10} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} {5A52D9FC-0059-4A4A-8196-427A7AA0D1C5} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {76B26B2C-602A-4AD0-9736-4162D3FCA92A} = {1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798} + {EF18BED6-EBC0-45E9-8D61-4202528E8AAB} = {1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798} {A0B4F808-B190-41C4-97CB-C8EA1932F84F} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} {A33223D2-550B-4D99-A53D-488B1F68683E} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 8dea7d9f4a..ad2925f603 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -121,8 +121,8 @@ Disabled _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) true true false @@ -148,7 +148,7 @@ _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) true false @@ -169,9 +169,9 @@ true true _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) true true true @@ -1116,9 +1116,6 @@ {ca460806-5e41-4e97-9a3d-1d74b433b663} - - {1b9077b3-8923-4ecd-8fc9-b3190fcbe4d4} - diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 5fa3628a34..ff8ac9f05e 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -204,8 +204,8 @@ Disabled _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) true true true @@ -224,7 +224,7 @@ _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) true true true @@ -240,9 +240,9 @@ true true _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) true true true @@ -271,9 +271,9 @@ true true _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\SfsClient\sfs-client\client\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) true true true diff --git a/src/SfsClient/SfsClient.vcxproj b/src/SfsClient/SfsClient.vcxproj deleted file mode 100644 index 9b50a5516e..0000000000 --- a/src/SfsClient/SfsClient.vcxproj +++ /dev/null @@ -1,291 +0,0 @@ - - - - 16.0 - {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4} - Win32Proj - true - 10.0.26100.0 - 10.0.17763.0 - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - StaticLibrary - true - - - StaticLibrary - false - Spectre - - - true - true - true - $(ProjectDir)..\vcpkg_installed - - - - - - - - - true - $(SolutionDir)$(PlatformShortname)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(PlatformShortname)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(PlatformShortname)\$(Configuration)\$(ProjectName)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - x64 - - - x64-release - - - x64-release-static - - - arm64-release-static - - - x86-release-static - - - arm64 - - - x86 - - - x86-release - - - arm64-release - - - - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";WIN32;_DEBUG;%(PreprocessorDefinitions) - TurnOffAllWarnings - ProgramDatabase - stdcpp17 - Disabled - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - true - - - MachineX86 - true - Windows - - - - - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";WIN32;NDEBUG;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - stdcpp17 - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - true - - - MachineX86 - true - Windows - true - true - - - - - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";WIN32;NDEBUG;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - stdcpp17 - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - MultiThreaded - true - - - MachineX86 - true - Windows - true - true - - - - - stdcpp17 - NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions) - Level3 - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - true - - - - - stdcpp17 - NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions) - Level3 - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - MultiThreaded - true - - - - - stdcpp17 - NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) - Level3 - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - true - - - - - stdcpp17 - NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) - Level3 - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - MultiThreaded - true - - - - - stdcpp17 - NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";_DEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) - TurnOffAllWarnings - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - true - - - - - stdcpp17 - NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";_DEBUG;%(PreprocessorDefinitions) - TurnOffAllWarnings - $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/SfsClient/SfsClient.vcxproj.filters b/src/SfsClient/SfsClient.vcxproj.filters deleted file mode 100644 index 6db39e4511..0000000000 --- a/src/SfsClient/SfsClient.vcxproj.filters +++ /dev/null @@ -1,159 +0,0 @@ - - - - - {7825c1db-0159-4653-b26c-bc674bb9e29f} - - - {357ffe7c-3ce6-4b0d-9e93-bbda28fb9fc3} - - - {d7879d14-b049-4837-a336-895bd20ab5d8} - - - {15462934-ddd8-46ad-a0fd-110df7fa2acb} - - - {133cf863-8baa-4ce2-9050-40155b027f04} - - - {93204abc-3d4c-4cf0-9433-db8c4ad667b5} - - - {102d246a-6b50-4171-a909-cb68923ac321} - - - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - include\sfsclient - - - - - src - - - src - - - src - - - src - - - src - - - src - - - src - - - src - - - src - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details - - - src\details\connection - - - src\details\connection - - - src\details\connection - - - src\details\connection - - - src\details\connection - - - src\details\connection - - - src\details\connection\mock - - - src\details\connection\mock - - - src\details\entity - - - src\details\entity - - - src\details\entity - - - diff --git a/src/SfsClient/readme.md b/src/SfsClient/readme.md deleted file mode 100644 index 84fcea431b..0000000000 --- a/src/SfsClient/readme.md +++ /dev/null @@ -1,19 +0,0 @@ -## SfsClient - -Do not change code under the sfs-client directory; it contains sfs-client source code from release 1.1.0 (https://github.com/microsoft/sfs-client/releases/tag/1.1.0). -It was initially created using git subtree command: -``` - git subtree add --prefix=src/SfsClient/sfs-client https://github.com/microsoft/sfs-client.git be733af9e5c8e9227f2018ff618800bf08a31180 --squash -``` -Then updated to release 1.1.0 using: -``` - git subtree pull -P src/SfsClient/sfs-client https://github.com/microsoft/sfs-client 1.1.0 --squash -``` - - -### Update -To update, run the following command, then update the above commit for reference. 'master' can be replaced with the appropriate commit spec as desired. -``` - git subtree pull -P src/SfsClient/sfs-client https://github.com/microsoft/sfs-client master --squash -``` -**When committing the PR, DO NOT squash it. The two commits are needed as is to allow for future subtree pulls.** diff --git a/src/SfsClient/sfs-client/.clang-format b/src/SfsClient/sfs-client/.clang-format deleted file mode 100644 index 4ee2a2ec17..0000000000 --- a/src/SfsClient/sfs-client/.clang-format +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# -- Usage -- -# Read README.md for initial context, and instructions for VSCode and command line. -# -# See here for the meaning of each item in this file: https://clang.llvm.org/docs/ClangFormatStyleOptions.html -# To get all the options that are part of a standard style such as Microsoft, do this: -# > clang-format --style=Microsoft -dump-config > clang-format-microsoft.txt -# -# -- Philosophy -- -# We will try to adhere to the standard Microsoft style as much as possible. -# The overrides below are deviations from the standard. Keep it short. ---- -Language: Cpp -BasedOnStyle: Microsoft - -AllowAllArgumentsOnNextLine: false -AllowAllParametersOfDeclarationOnNextLine: false -AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: Yes -BinPackArguments: false -BinPackParameters: false -BraceWrapping: - AfterCaseLabel: true -BreakConstructorInitializers: BeforeComma -BreakStringLiterals: false -InsertNewlineAtEOF: true -PackConstructorInitializers: CurrentLine -PointerAlignment: Left diff --git a/src/SfsClient/sfs-client/.cmake-format.json b/src/SfsClient/sfs-client/.cmake-format.json deleted file mode 100644 index a06983a3f8..0000000000 --- a/src/SfsClient/sfs-client/.cmake-format.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "first_comment_is_literal": "true", - "keyword_case": "upper", - "line_ending": "windows", - "max_pargs_hwrap": 3, - "tab_size": 4 -} \ No newline at end of file diff --git a/src/SfsClient/sfs-client/.github/CODEOWNERS b/src/SfsClient/sfs-client/.github/CODEOWNERS deleted file mode 100644 index 69e67dd6cb..0000000000 --- a/src/SfsClient/sfs-client/.github/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# The SFS Client Reviewers team will be requested for review when someone opens a pull request. -* @microsoft/sfs-client-reviewers \ No newline at end of file diff --git a/src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/bug_report.md b/src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 74514987e3..0000000000 --- a/src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Additional context** -Add any other context about the problem here. diff --git a/src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/feature_request.md b/src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7d61..0000000000 --- a/src/SfsClient/sfs-client/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/src/SfsClient/sfs-client/.github/dependabot.yml b/src/SfsClient/sfs-client/.github/dependabot.yml deleted file mode 100644 index c64b5ab54b..0000000000 --- a/src/SfsClient/sfs-client/.github/dependabot.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Dependabot helps maintain dependencies updated for different package ecosystems -# -# Here is the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - - # Maintain dependencies for pip - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - ignore: - # Ignore patch updates for all pip dependencies - - dependency-name: "*" - update-types: ["version-update:semver-patch"] diff --git a/src/SfsClient/sfs-client/.github/pull_request_template.md b/src/SfsClient/sfs-client/.github/pull_request_template.md deleted file mode 100644 index ddaa8e6abe..0000000000 --- a/src/SfsClient/sfs-client/.github/pull_request_template.md +++ /dev/null @@ -1,15 +0,0 @@ -#### Related Issues - -- Closes # -- Helps # - -#### Why is this change being made? - -#### What is being changed? - -#### How was the change tested? - - - - - diff --git a/src/SfsClient/sfs-client/.github/workflows/initialize-codeql/action.yml b/src/SfsClient/sfs-client/.github/workflows/initialize-codeql/action.yml deleted file mode 100644 index 990b0603bf..0000000000 --- a/src/SfsClient/sfs-client/.github/workflows/initialize-codeql/action.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Initialize CodeQL - -description: Initializes CodeQL action to be used in build workflows - -runs: - using: "composite" - - steps: - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: cpp \ No newline at end of file diff --git a/src/SfsClient/sfs-client/.github/workflows/install-winget/action.yml b/src/SfsClient/sfs-client/.github/workflows/install-winget/action.yml deleted file mode 100644 index 55847ae2ce..0000000000 --- a/src/SfsClient/sfs-client/.github/workflows/install-winget/action.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Install WinGet - -description: Installs WinGet from sources, caching the installation files if possible - -inputs: - winget-installation-cache: - description: 'Where the installation files will be cached' - required: false - default: 'winget-installation-cache' - -runs: - using: "composite" - - steps: - - name: Cache WinGet installation - id: cache-winget-installation - uses: actions/cache@v4 - with: - path: winget-installation-cache - key: winget-installation - - # Winget is not available in the windows-latest image as it is Windows Server 2022 and winget is not yet available officially for it. - # We can still install it in "experimental" mode manually though. - - name: Install WinGet - shell: pwsh - run: | - # Check it doesn't exist to avoid installing it without need - if (!(Get-Command 'winget.exe' -CommandType Application -ErrorAction SilentlyContinue)) - { - New-Item -ItemType Directory ${{ inputs.winget-installation-cache }} -Force | Out-Null - Set-Location ${{ inputs.winget-installation-cache }} - $ProgressPreference = "silentlyContinue" - $WingetInstaller = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" - if (!(Test-Path $WingetInstaller)) - { - Write-Host "Downloading Winget" - Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.7.10582/$WingetInstaller -OutFile $WingetInstaller - } - $VCLibsInstaller = "Microsoft.VCLibs.x64.14.00.Desktop.appx" - if (!(Test-Path $VCLibsInstaller)) - { - Write-Host "Downloading Winget dependency VCLibs" - Invoke-WebRequest -Uri https://aka.ms/$VCLibsInstaller -OutFile $VCLibsInstaller - } - $XamlInstaller = "Microsoft.UI.Xaml.2.8.x64.appx" - if (!(Test-Path $XamlInstaller)) - { - Write-Host "Downloading Winget dependency Xaml" - Invoke-WebRequest -Uri https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/$XamlInstaller -OutFile $XamlInstaller - } - - Import-Module -Name Appx -UseWindowsPowershell | Out-Null - Add-AppxPackage $VCLibsInstaller - Add-AppxPackage $XamlInstaller - Add-AppxPackage $WingetInstaller - } diff --git a/src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml b/src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml deleted file mode 100644 index 59ee8521c9..0000000000 --- a/src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Main Build (Ubuntu) - -on: - push: - branches: [ "main" ] - -# Permissions and environment values to be able to update the dependency graph with vcpkg information -# and to enable the writing/uploading of CodeQL scan results -permissions: - contents: write - security-events: write - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VCPKG_FEATURE_FLAGS: dependencygraph - -jobs: - build-ubuntu: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: ./.github/workflows/initialize-codeql - - - name: Setup - run: source ./scripts/setup.sh - - - name: Build and Test (no test overrides) - run: | - ./scripts/build.sh - ./scripts/test.sh --output-on-failure - - - name: Build and Test (with test overrides) - run: | - ./scripts/build.sh --enable-test-overrides - ./scripts/test.sh --output-on-failure - - - name: Build and Test (Release) - run: | - ./scripts/build.sh --build-type Release - ./scripts/test.sh --output-on-failure - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml b/src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml deleted file mode 100644 index 0c2a6cf653..0000000000 --- a/src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Main Build (Windows) - -on: - push: - branches: [ "main" ] - -# Permissions and environment values to be able to update the dependency graph with vcpkg information -# and to enable the writing/uploading of CodeQL scan results -permissions: - contents: write - security-events: write - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VCPKG_FEATURE_FLAGS: dependencygraph - -jobs: - build-windows: - runs-on: 'windows-latest' - - steps: - - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: ./.github/workflows/initialize-codeql - - - name: Install Winget - uses: ./.github/workflows/install-winget - - - name: Setup - shell: pwsh - run: .\scripts\Setup.ps1 - - - name: Build and Test (no test overrides) - shell: pwsh - run: | - .\scripts\Build.ps1 - .\scripts\Test.ps1 -OutputOnFailure - - - name: Build and Test (with test overrides) - shell: pwsh - run: | - .\scripts\Build.ps1 -EnableTestOverrides - .\scripts\Test.ps1 -OutputOnFailure - - - name: Build and Test (Release) - shell: pwsh - run: | - .\scripts\Build.ps1 -BuildType Release - .\scripts\Test.ps1 -OutputOnFailure - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/src/SfsClient/sfs-client/.github/workflows/pr.yml b/src/SfsClient/sfs-client/.github/workflows/pr.yml deleted file mode 100644 index f3740d36c4..0000000000 --- a/src/SfsClient/sfs-client/.github/workflows/pr.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: PR Build - -env: - # Set up vcpkg to read from the GitHub cache (https://learn.microsoft.com/en-us/vcpkg/consume/binary-caching-github-actions-cache) - VCPKG_BINARY_SOURCES: 'clear;x-gha,readwrite' - -on: - pull_request: - branches: [ "main" ] - -jobs: - build-windows: - runs-on: 'windows-latest' - - steps: - - uses: actions/checkout@v4 - - - name: Set up required environment variables for vcpkg cache - uses: actions/github-script@v7 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Initialize CodeQL - uses: ./.github/workflows/initialize-codeql - - - name: Install Winget - uses: ./.github/workflows/install-winget - - - name: Setup - shell: pwsh - run: .\scripts\Setup.ps1 - - - name: Check formatting - shell: pwsh - run: python .\scripts\check-format.py - - - name: Build and Test (no test overrides) - shell: pwsh - run: | - .\scripts\Build.ps1 - .\scripts\Test.ps1 -OutputOnFailure - - - name: Build and Test (with test overrides) - shell: pwsh - run: | - .\scripts\Build.ps1 -EnableTestOverrides - .\scripts\Test.ps1 -OutputOnFailure - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - - build-ubuntu: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up required environment variables for vcpkg cache - uses: actions/github-script@v7 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Initialize CodeQL - uses: ./.github/workflows/initialize-codeql - - - name: Setup - run: source ./scripts/setup.sh - - - name: Check formatting - run: python ./scripts/check-format.py - - - name: Build and Test (no test overrides) - run: | - ./scripts/build.sh - ./scripts/test.sh --output-on-failure - - - name: Build and Test (with test overrides) - run: | - ./scripts/build.sh --enable-test-overrides - ./scripts/test.sh --output-on-failure - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/src/SfsClient/sfs-client/.gitignore b/src/SfsClient/sfs-client/.gitignore deleted file mode 100644 index 934e6cbb7d..0000000000 --- a/src/SfsClient/sfs-client/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# Build -/build - -# VSCode specific folder -/.vscode - -# Vcpkg -/vcpkg -/vcpkg_installed - -# Formatting temporary file -/.tmp_formatted diff --git a/src/SfsClient/sfs-client/API.md b/src/SfsClient/sfs-client/API.md deleted file mode 100644 index acc930ad3c..0000000000 --- a/src/SfsClient/sfs-client/API.md +++ /dev/null @@ -1,59 +0,0 @@ -## SFSClient - -To start using the SFSClient library, use `SFSClient::Make()` to create an `SFSClient` instance, which allows you to use the SFS APIs. -The first argument to the factory is a `ClientConfig` struct. Configuring this struct allows you to customize the behavior of the client. -Refer to the documentation of the `ClientConfig` struct in [ClientConfig.h](client/include/sfsclient/ClientConfig.h) to see the available options. - -## Logging Callback - -To retrieve logging information from the API, set a logging callback in `ClientConfig::logCallbackFn` when constructing an SFSClient instance with `SFSClient::Make()`. - -The logging callback function has the signature: - -```cpp -void callback(const SFS::LogData&); -``` - -An example to log the data directly to the standard output using `std::cout`: - -```cpp -void LoggingCallback(const SFS::LogData& logData) -{ - std::cout << "Log: [" << ToString(logData.severity) << "]" << " " << logData.file << ":" - << logData.line << " " << logData.message << std::endl; -} -``` - -Notes: -- The callback itself is processed in the main thread. Do not use a blocking callback. If heavy processing has to be done, consider capturing the data and processing another thread. -- The LogData contents only exist within the callback call. If the processing will be done later, you should copy the data elsewhere. -- The callback should not do any re-entrant calls (e.g. call `SFSClient` methods). - -## Class instances - -It is recommended to only create a single `SFSClient` instance, even if multiple threads will be used. -Each `GetLatestDownloadInfo()` call will create its own connection and should not interfere with other calls. - -### Thread safety - -All API calls are thread-safe. - -If a logging callback is set in a multi-threaded environment, and the same `SFSClient()` is reused across different threads, the same callback will be called by all usages of the class. So, make sure the callback itself is also thread-safe. - -## Content types - -A few data types are provided which abstract contents that can be sent by the SFS Service, such as `Content`, `ContentId`, `File`. -These data types provide `noexcept` methods to interact with member data. - -## Retry Behavior - -The API follows a certain set of rules to retry upon reaching specific HTTP Status Codes. The behavior is configurable through the `retryOnError` member of `RequestParams`. - -By default, the client will retry up to 3 times when reaching the following HTTP Status Codes: -- 429: Too Many Requests -- 500: Internal Server Error -- 502: Bad Gateway -- 503: Server Busy -- 504: Gateway Timeout - -Between each retry the Client will wait an interval that follows either the `Retry-After` response header, or an exponential backoff calculation with a factor of 2 starting from 15s. diff --git a/src/SfsClient/sfs-client/CMakeLists.txt b/src/SfsClient/sfs-client/CMakeLists.txt deleted file mode 100644 index 31bb1010cd..0000000000 --- a/src/SfsClient/sfs-client/CMakeLists.txt +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -cmake_minimum_required(VERSION 3.19) - -# Tell CMake to look in cmake for our custom CMake module files. -set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - -include(SFSOptions) - -if(SFS_BUILD_TESTS) - list(APPEND VCPKG_MANIFEST_FEATURES "tests") -endif() - -set(CMAKE_TOOLCHAIN_FILE "vcpkg/scripts/buildsystems/vcpkg.cmake") - -# By default using x64 static custom triplet for Windows. Can be overridden by -# setting VCPKG_TARGET_TRIPLET -if(SFS_WINDOWS_STATIC_ONLY - AND WIN32 - AND NOT DEFINED VCPKG_TARGET_TRIPLET) - set(VCPKG_TARGET_TRIPLET "x64-windows-static-custom") -endif() - -# Semver versioning. Given MAJOR.MINOR.PATCH, increment the: -# -# 1. MAJOR version when you make incompatible API changes -# 2. MINOR version when you add functionality in a backward compatible manner -# 3. PATCH version when you make backward compatible bug fixes -set(SFS_LIBRARY_VERSION "1.1.0") - -project( - sfsclient - VERSION ${SFS_LIBRARY_VERSION} - LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -# Use this function to set warning level and warnings as errors for a given -# target -function(set_compile_options_for_target target) - if(MSVC) - # Enable some MSVC warnings that are off by default: - # - # * 4062: all enums are handled in switch statements without a default - # label - # * 4191: unsafe conversion from 'type of expression' to 'type required' - # * 4242: possible loss of data (identifier) - # * 4254: possible loss of data (operator) - # * 4287: 'operator': unsigned/negative constant mismatch - # * 4296: 'operator': expression is always true (or false) - # * 4388: signed/unsigned mismatch - # * 4800: implicit conversion to bool; possible information loss - # * 4946: reinterpret_cast used between related classes - - # cmake-format: off - target_compile_options(${target} PRIVATE /W4 /WX /we4062 /we4191 /we4242 /we4254 /we4287 /we4296 /we4388 /we4800 /we4946) - # cmake-format: on - else() - target_compile_options( - ${target} - PRIVATE -Wall - -Wextra - -Wpedantic - -Werror) - endif() -endfunction() - -if(SFS_WINDOWS_STATIC_ONLY AND MSVC) - # For MSVC, use a multi-threaded statically-linked runtime library - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -endif() - -add_subdirectory(client) - -if(SFS_BUILD_SAMPLES) - add_subdirectory(samples) -endif() diff --git a/src/SfsClient/sfs-client/CODE_OF_CONDUCT.md b/src/SfsClient/sfs-client/CODE_OF_CONDUCT.md deleted file mode 100644 index f9ba8cf65f..0000000000 --- a/src/SfsClient/sfs-client/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,9 +0,0 @@ -# Microsoft Open Source Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -Resources: - -- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) -- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/src/SfsClient/sfs-client/DEVELOPMENT.md b/src/SfsClient/sfs-client/DEVELOPMENT.md deleted file mode 100644 index 015159ac88..0000000000 --- a/src/SfsClient/sfs-client/DEVELOPMENT.md +++ /dev/null @@ -1,6 +0,0 @@ -# cgmanifest.json - -Keep this file updated as you update `vcpkg.json`. It must indicate the dependencies we use for proper automatic scanning of dependencies done in the Azure DevOps backend. -The folder `build\vcpkg_installed\vcpkg\info` is a good source of truth of which vcpkg packages are installed. It does however accumulate temporary packages you may have added in the past. Before using it, try cleaning the build folder and re-building. - -Once you find the GitHub repo of the dependency, use the version on the tags page to find the commit hash that corresponds to the installed version. diff --git a/src/SfsClient/sfs-client/LICENSE b/src/SfsClient/sfs-client/LICENSE deleted file mode 100644 index 9e841e7a26..0000000000 --- a/src/SfsClient/sfs-client/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE diff --git a/src/SfsClient/sfs-client/NOTICE.md b/src/SfsClient/sfs-client/NOTICE.md deleted file mode 100644 index da464a7d05..0000000000 --- a/src/SfsClient/sfs-client/NOTICE.md +++ /dev/null @@ -1,441 +0,0 @@ -# NOTICES - -This repository incorporates material as listed below or described in the code. - ---------------------------------------------------------- - -**Component:** - -CorrelationVector-Cpp - -**Open Source License/Copyright Notice:** - -MIT License - -Copyright (c) Microsoft Corporation. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE - ---------------------------------------------------------- - -**Component:** - -curl - -**Open Source License/Copyright Notice:** - -COPYRIGHT AND PERMISSION NOTICE - -Copyright (c) 1996 - 2024, Daniel Stenberg, , and many -contributors, see the THANKS file. - -All rights reserved. - -Permission to use, copy, modify, and distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright -notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN -NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of a copyright holder shall not -be used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization of the copyright holder. - ---------------------------------------------------------- - -**Component:** - -c-ares - -**Open Source License/Copyright Notice:** - -MIT License - -Copyright (c) 1998 Massachusetts Institute of Technology Copyright (c) 2007 - 2023 Daniel Stenberg with many contributors, see AUTHORS file. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -**Additional Attribution:** - -c-ares is based on ares, and these are the people that have worked on it since -the fork was made: - -Albert Chin -Alex Loukissas -Alexander Klauer -Alexander Lazic -Alexey Simak -Andreas Rieke -Andrew Andkjar -Andrew Ayer -Andrew C. Morrow -Ashish Sharma -Ben Greear -Ben Noordhuis -BogDan Vatra -Brad House -Brad Spencer -Bram Matthys -Chris Araman -Dan Fandrich -Daniel Johnson -Daniel Stenberg -David Drysdale -David Stuart -Denis Bilenko -Dima Tisnek -Dirk Manske -Dominick Meglio -Doug Goldstein -Doug Kwan -Duncan Wilcox -Eino Tuominen -Erik Kline -Fedor Indutny -Frederic Germain -Geert Uytterhoeven -George Neill -Gisle Vanem -Google LLC -Gregor Jasny -Guenter Knauf -Guilherme Balena Versiani -Gunter Knauf -Henrik Stoerner -Jakub Hrozek -James Bursa -Jérémy Lal -John Schember -Keith Shaw -Lei Shi -Marko Kreen -Michael Wallner -Mike Crowe -Nick Alcock -Nick Mathewson -Nicolas "Pixel" Noble -Ning Dong -Oleg Pudeyev -Patrick Valsecchi -Patrik Thunstrom -Paul Saab -Peter Pentchev -Phil Blundell -Poul Thomas Lomholt -Ravi Pratap -Robin Cornelius -Saúl Ibarra Corretgé -Sebastian at basti79.de -Shmulik Regev -Stefan Bühler -Steinar H. Gunderson -Svante Karlsson -Tofu Linden -Tom Hughes -Tor Arntsen -Viktor Szakats -Vlad Dinulescu -William Ahern -Yang Tse -hpopescu at ixiacom.com -liren at vivisimo.com -nordsturm -saghul - ---------------------------------------------------------- - -**Component:** - -openssl - -**Open Source License/Copyright Notice:** - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        https://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
- -**Additional Attribution:** - -
-
-Authors
-=======
-
-This is the list of OpenSSL authors for copyright purposes.
-It does not necessarily list everyone who has contributed code,
-since in some cases, their employer may be the copyright holder.
-To see the full list of contributors, see the revision history in
-source control.
-
-Groups
-------
-
- * OpenSSL Software Services, Inc.
- * OpenSSL Software Foundation, Inc.
-
-Individuals
------------
-
- * Andy Polyakov
- * Ben Laurie
- * Ben Kaduk
- * Bernd Edlinger
- * Bodo Möller
- * David Benjamin
- * David von Oheimb
- * Dmitry Belyavskiy (Дмитрий Белявский)
- * Emilia Käsper
- * Eric Young
- * Geoff Thorpe
- * Holger Reif
- * Kurt Roeckx
- * Lutz Jänicke
- * Mark J. Cox
- * Matt Caswell
- * Matthias St. Pierre
- * Nicola Tuveri
- * Nils Larsch
- * Patrick Steuer
- * Paul Dale
- * Paul C. Sutton
- * Paul Yang
- * Ralf S. Engelschall
- * Rich Salz
- * Richard Levitte
- * Shane Lontis
- * Stephen Henson
- * Steve Marquess
- * Tim Hudson
- * Tomáš Mráz
- * Ulf Möller
- * Viktor Dukhovni
-
- ---------------------------------------------------------- - -**Component:** nlohmann/json - -**Open Source License/Copyright Notice:** - -MIT License - -Copyright (c) 2013-2022 Niels Lohmann - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/SfsClient/sfs-client/README.md b/src/SfsClient/sfs-client/README.md deleted file mode 100644 index c16717c799..0000000000 --- a/src/SfsClient/sfs-client/README.md +++ /dev/null @@ -1,195 +0,0 @@ -# SFS Client - -[![Windows Latest](https://github.com/microsoft/sfs-client/actions/workflows/main-build-windows.yml/badge.svg?branch=main&event=push)](https://github.com/microsoft/sfs-client/actions/workflows/main-build-windows.yml) [![Ubuntu Latest](https://github.com/microsoft/sfs-client/actions/workflows/main-build-ubuntu.yml/badge.svg?branch=main&event=push)](https://github.com/microsoft/sfs-client/actions/workflows/main-build-ubuntu.yml) - -## Introduction - -This repository holds the Simple File Solution (SFS) Client, a C++ library that simplifies the interface with the SFS service. -Read below to get started on developing and using the library. - -## Usage - -Follow the [API](API.md) document for tips on how to use the API. - -## Getting Started - -### Prerequisites - -#### Setup script - -There are a few dependencies required to work on this project. -To set them up, use the Setup script. - -Windows: -```powershell -.\scripts\Setup.ps1 -``` - -Linux: -```bash -source ./scripts/setup.sh -``` - -The script can be run multiple times as it does not replace what has been installed, and updates dependencies. -It also sets up useful command-line aliases that can be used while developing. - -## Consuming the library - -This library is distributed as Source Code and meant for consumption in this format. Below we outline how to easily consume us through the vcpkg tool, but feel free to use other methods to incorporate the source. - -### vcpkg - -The [vcpkg](https://vcpkg.io/) tool is a Microsoft dependency manager for C/C++. It works "for all platforms, buildsystems, and workflows". -In general the dependencies are registered in the central vcpkg registry, where the "portfile" recipes are hosted. These files indicate the way the dependencies should be acquired and built. - -One of the features it provides is also a way to find dependencies listed in the local filesystem. See the [overlay-ports](https://learn.microsoft.com/en-us/vcpkg/concepts/overlay-ports) feature to see that. -We are not hosted in the central vcpkg registry, but we provide a template overlay-port for easy consumption of the library. See the [sfs-client-vcpkg-port](./sfs-client-vcpkg-port) folder for the files you need to have in your local repository in order to consume us. A few placeholders have to be filled in on those files. - -## Formatting - -This project is currently using the clang-format tool to format its source code according to predefined rules. -It is also using cmake-format to format the CMakeLists.txt files. -Both are installed automatically with the Setup script. - -### Automatic usage - -The project is configured to automatically run formatting upon committing so nobody introduces -unformatted changes to the codebase. This is done through a pre-commit hook. -If you must avoid the hook, you can use `git commit -n` to bypass it. - -### Running on command line - -Use -h to see the binary help: -``` -clang-format -h -cmake-format -h -``` - -Use -i to edit a file inplace: -``` -clang-format -i ./interface.cpp -cmake-format -i ./CMakeLists.txt -``` - -Wildcards are accepted in clang-format: -``` -clang-format -i ./*.h -``` - -## Building - -To build, use the `build` command. It simplifies the CMake build commands and re-generates CMake configurations if needed. - -Available build options: - -| Switch (PowerShell) | Switch (Bash) | Description | -|----------------------|---------------------------|------------------------------------------------------------------------------------------| -| -Clean | --clean | Use this to clean the build folder before building. | -| -BuildType | --build-type | Use this to define the build type between "Debug" and "Release". The default is "Debug". | -| -EnableTestOverrides | --enable-test-overrides | Use this to enable test overrides. See [TEST](TEST.md) for more. | -| -BuildTests | --build-tests {ON, OFF} | Use this to build tests alongside the library. On by default. | -| -BuildSamples | --build-samples {ON, OFF} | Use this to build samples alongside the library. On by default. | - -See [below](#building-with-cmake-vscode-extension) for building within VSCode. - -## VSCode - -[Visual Studio Code](https://code.visualstudio.com) is the recommended editor to work with this project. -But you're free to use other editors and command-line tools. - -### Configuring VSCode includes with CMake - -VSCode has a great integration with CMake through the CMake Tools extension (ms-vscode.cmake-tools). -It allows you to configure and build the project through the UI. - -Open the command pane on VSCode and search for "C/C++: Edit Configurations (JSON)" to create a c_cpp_properties.json file under a .vscode folder in repo root. -The folder is ignored in git by default. -Add the following line to the "configurations" element to make VSCode start using the includes defined in the CMakeLists.txt files. - -```json -"configurations": [ - { - "configurationProvider": "ms-vscode.cmake-tools" - } -] -``` - -### Formatting C++ Sources with VSCode - -Install the extension https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools and Shift+Alt+F inside a file. -You can also access "Format" options by right clicking on an open file or over a line selection. - -If you want it, you can also make VSCode format on each Save operation by adding this to your User JSON settings: -`"editor.formatOnSave": true` - -### vcpkg integration - -From [the vcpkg docs](https://github.com/Microsoft/vcpkg/#visual-studio-code-with-cmake-tools): - -Adding the following to your workspace settings.json will make CMake Tools automatically use vcpkg for libraries: - -```json -{ - "cmake.configureSettings": { - "CMAKE_TOOLCHAIN_FILE": "[vcpkg root]/scripts/buildsystems/vcpkg.cmake" - } -} -``` - -### Building with CMake VSCode extension - -If you're using the CMake Tools extension on VSCode, you can set the build options through the VSCode settings. Add something like below to either your user or workspace JSON settings to get a default value for an option. You can also later use the command "Edit CMake Cache (UI)" for visual editing. - -```json -"cmake.configureArgs": [ - "-DSFS_ENABLE_TEST_OVERRIDES=ON" -] -``` - -See [SFSOptions.cmake](cmake/SFSOptions.cmake) for the CMake options available for the library. - -## Testing - -Tests are compiled alongside the library by default, and live in the client/tests subdirectory. -To run the tests, you can use the `test` command. It will run all tests directly and output the result to the console. - -If you want to customize the test run, you can make use of the `ctest` tool. - -``` -ctest --test-dir ./build/client -``` - -To run specific tests, you can filter the chosen tests through the switch `-R` or `--tests-regex`. -For more test selection switches, use `ctest --help`. - -The tests are built using the Catch2 framework. For a more verbose run you can run the executable directly, with -s. - -Windows: -``` -.\build\tests\bin\\SFSClientTests.exe -s -``` - -Linux: -``` -./build/tests/bin/SFSClientTests -s -``` - -Follow the [TEST](TEST.md) document for more information regarding testing. - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. - -When you submit a pull request, a CLA bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## Trademarks - -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft’s Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies. diff --git a/src/SfsClient/sfs-client/SECURITY.md b/src/SfsClient/sfs-client/SECURITY.md deleted file mode 100644 index b3c89efc85..0000000000 --- a/src/SfsClient/sfs-client/SECURITY.md +++ /dev/null @@ -1,41 +0,0 @@ - - -## Security - -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). - -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. - -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). - -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). - -Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. - -## Preferred Languages - -We prefer all communications to be in English. - -## Policy - -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). - - diff --git a/src/SfsClient/sfs-client/SUPPORT.md b/src/SfsClient/sfs-client/SUPPORT.md deleted file mode 100644 index 85c3de9c9f..0000000000 --- a/src/SfsClient/sfs-client/SUPPORT.md +++ /dev/null @@ -1,11 +0,0 @@ -# Support - -## How to file issues and get help - -This project uses GitHub Issues to track bugs and feature requests. Please search the existing -issues before filing new issues to avoid duplicates. For new issues, file your bug or -feature request as a new Issue. - -## Microsoft Support Policy - -Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/src/SfsClient/sfs-client/TEST.md b/src/SfsClient/sfs-client/TEST.md deleted file mode 100644 index 99406e95c5..0000000000 --- a/src/SfsClient/sfs-client/TEST.md +++ /dev/null @@ -1,12 +0,0 @@ -## Testing overrides - -Some test behaviors are controlled by test overrides set via environment variables. -They only work when the CMake Option `SFS_ENABLE_TEST_OVERRIDES` is set to `ON`. -To enable that during build, you must use the switch `-EnableTestOverrides` (`--enable-test-overrides` on Linux) with the build scripts. Click [here](README.md#building) for more. See also [here](README.md#building-with-cmake-vscode-extension) for how to set when using VSCode. - -When the test overrides are enabled, a few environment variables can be used to adjust the behavior of the tool: - -| Environment Variable | Description | -|-----------------------------------------------|---------------------------------------------------------------------------------------------| -| SFS_TEST_BASE_RETRY_DELAY_MS | Set this to override the base retry delay of 15s. | -| SFS_TEST_OVERRIDE_BASE_URL | Set this to any string value which will be used as the SFS URL rather than the default one. | diff --git a/src/SfsClient/sfs-client/cgmanifest.json b/src/SfsClient/sfs-client/cgmanifest.json deleted file mode 100644 index 355bf846a5..0000000000 --- a/src/SfsClient/sfs-client/cgmanifest.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/component-detection-manifest.json", - "version": 1, - "registrations": [ - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/catchorg/Catch2", - "commitHash": "6e79e682b726f524310d55dec8ddac4e9c52fb5f" - } - }, - "developmentDependency": true - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/microsoft/CorrelationVector-Cpp", - "commitHash": "cf38d2b44baaf352509ad9980786bc49554c32e4" - } - }, - "developmentDependency": false - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/yhirose/cpp-httplib", - "commitHash": "5c00bbf36ba8ff47b4fb97712fc38cb2884e5b98" - } - }, - "developmentDependency": true - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/curl/curl", - "commitHash": "83bedbd730d62b83744cc26fa0433d3f6e2e4cd6" - } - }, - "developmentDependency": false - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/c-ares/c-ares", - "commitHash": "fddf01938d3789e06cc1c3774e4cd0c7d2a89976" - } - }, - "developmentDependency": false, - "dependencyRoots": [ - { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/curl/curl", - "commitHash": "83bedbd730d62b83744cc26fa0433d3f6e2e4cd6" - } - } - ] - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/microsoft/do-client", - "commitHash": "d71ade6f692dd8bc319ec3228c956517e9b29292" - } - }, - "developmentDependency": true - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/nlohmann/json", - "commitHash": "9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03" - } - }, - "developmentDependency": false - } - ] -} \ No newline at end of file diff --git a/src/SfsClient/sfs-client/client/CMakeLists.txt b/src/SfsClient/sfs-client/client/CMakeLists.txt deleted file mode 100644 index e2fd215ff9..0000000000 --- a/src/SfsClient/sfs-client/client/CMakeLists.txt +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# HTTP stack library -find_package(CURL REQUIRED) - -# JSON library -find_package(nlohmann_json CONFIG REQUIRED) - -# CorrelationVector Library from Microsoft -find_package(correlation_vector CONFIG REQUIRED) - -add_library(${PROJECT_NAME} STATIC) -add_library(Microsoft::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) - -target_sources( - ${PROJECT_NAME} - PRIVATE src/AppContent.cpp - src/AppFile.cpp - src/ApplicabilityDetails.cpp - src/Content.cpp - src/ContentId.cpp - src/details/connection/Connection.cpp - src/details/connection/ConnectionConfig.cpp - src/details/connection/ConnectionManager.cpp - src/details/connection/CurlConnection.cpp - src/details/connection/CurlConnectionManager.cpp - src/details/connection/HttpHeader.cpp - src/details/connection/mock/MockConnection.cpp - src/details/connection/mock/MockConnectionManager.cpp - src/details/ContentUtil.cpp - src/details/CorrelationVector.cpp - src/details/entity/ContentType.cpp - src/details/entity/FileEntity.cpp - src/details/entity/VersionEntity.cpp - src/details/Env.cpp - src/details/ErrorHandling.cpp - src/details/OSInfo.cpp - src/details/ReportingHandler.cpp - src/details/SFSClientImpl.cpp - src/details/SFSException.cpp - src/details/SFSUrlBuilder.cpp - src/details/TestOverride.cpp - src/details/UrlBuilder.cpp - src/details/Util.cpp - src/File.cpp - src/Logging.cpp - src/Result.cpp - src/SFSClient.cpp) - -# Include dir for the library depends on whether the library is being built or -# if it was installed -target_include_directories( - ${PROJECT_NAME} - PUBLIC $ - $ # /include -) - -target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl) -target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json) -target_link_libraries(${PROJECT_NAME} PRIVATE microsoft::correlation_vector) - -# Pick up git revision during configuration to add to logging -include(FindGit) -if(GIT_FOUND) - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE SFS_GIT_HEAD_NAME - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE SFS_GIT_HEAD_REVISION - OUTPUT_STRIP_TRAILING_WHITESPACE) - - target_compile_definitions( - ${PROJECT_NAME} - PRIVATE SFS_GIT_INFO="${SFS_GIT_HEAD_NAME}:${SFS_GIT_HEAD_REVISION}") -endif() - -set_compile_options_for_target(${PROJECT_NAME}) - -target_compile_definitions(${PROJECT_NAME} - PRIVATE SFS_VERSION="${SFS_LIBRARY_VERSION}") - -if(SFS_ENABLE_TEST_OVERRIDES) - target_compile_definitions(${PROJECT_NAME} - PRIVATE SFS_ENABLE_TEST_OVERRIDES=1) -endif() - -if(SFS_BUILD_TESTS) - # Enables one to run tests through "ctest --test-dir .\build\client" - enable_testing() - - add_subdirectory(tests) -endif() - -# -# Install section -# - -set(_INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/sfsclient") -set(_TARGET_CONFIG_CMAKE_FILEPATH - "${CMAKE_CURRENT_BINARY_DIR}/sfsclient-config.cmake") - -# Install headers -install( - FILES include/sfsclient/AppContent.h - include/sfsclient/AppFile.h - include/sfsclient/ApplicabilityDetails.h - include/sfsclient/ClientConfig.h - include/sfsclient/Content.h - include/sfsclient/ContentId.h - include/sfsclient/File.h - include/sfsclient/Logging.h - include/sfsclient/RequestParams.h - include/sfsclient/Result.h - include/sfsclient/SFSClient.h - DESTINATION include/sfsclient) - -# Export targets for this library to a local file -install( - TARGETS ${PROJECT_NAME} - EXPORT sfsclient-targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -# And then install the exported targets file to the destination -install( - EXPORT sfsclient-targets - FILE sfsclient-targets.cmake - NAMESPACE microsoft:: - DESTINATION ${_INSTALL_DESTINATION}) - -# Add configure_package_config_file for below -include(CMakePackageConfigHelpers) - -# Create the sfsclient-config.cmake file, which will be used by find_package() -configure_package_config_file( - ../cmake/sfsclient-config.cmake.in ${_TARGET_CONFIG_CMAKE_FILEPATH} - INSTALL_DESTINATION ${_INSTALL_DESTINATION}) - -# Then install the generated file to the destination -install(FILES ${_TARGET_CONFIG_CMAKE_FILEPATH} - DESTINATION ${_INSTALL_DESTINATION}) diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/AppContent.h b/src/SfsClient/sfs-client/client/include/sfsclient/AppContent.h deleted file mode 100644 index e70eb538ba..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/AppContent.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "AppFile.h" -#include "Content.h" - -#include -#include -#include - -namespace SFS -{ -class AppPrerequisiteContent -{ - public: - [[nodiscard]] static Result Make(std::unique_ptr&& contentId, - std::vector&& files, - std::unique_ptr& out) noexcept; - - AppPrerequisiteContent(AppPrerequisiteContent&&) noexcept; - - AppPrerequisiteContent(const AppPrerequisiteContent&) = delete; - AppPrerequisiteContent& operator=(const AppPrerequisiteContent&) = delete; - - /** - * @return Unique content identifier - */ - const ContentId& GetContentId() const noexcept; - - /** - * @return Files belonging to this Prequisite - */ - const std::vector& GetFiles() const noexcept; - - private: - AppPrerequisiteContent() = default; - - std::unique_ptr m_contentId; - std::vector m_files; -}; - -class AppContent -{ - public: - [[nodiscard]] static Result Make(std::unique_ptr&& contentId, - std::string updateId, - std::vector&& prerequisites, - std::vector&& files, - std::unique_ptr& out) noexcept; - - AppContent(AppContent&&) noexcept; - - AppContent(const AppContent&) = delete; - AppContent& operator=(const AppContent&) = delete; - - /** - * @return Unique content identifier - */ - const ContentId& GetContentId() const noexcept; - - /** - * @return Unique Update Id - */ - const std::string& GetUpdateId() const noexcept; - - /** - * @return Files belonging to this App - */ - const std::vector& GetFiles() const noexcept; - - /** - * @return List of Prerequisite content needed for this App. Prerequisites don't have further dependencies. - */ - const std::vector& GetPrerequisites() const noexcept; - - private: - AppContent() = default; - - std::unique_ptr m_contentId; - std::vector m_files; - - std::string m_updateId; - std::vector m_prerequisites; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/AppFile.h b/src/SfsClient/sfs-client/client/include/sfsclient/AppFile.h deleted file mode 100644 index 35e4f94ee2..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/AppFile.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "ApplicabilityDetails.h" -#include "File.h" - -#include -#include -#include -#include - -namespace SFS -{ -class AppFile : private File -{ - public: - [[nodiscard]] static Result Make(std::string fileId, - std::string url, - uint64_t sizeInBytes, - std::unordered_map hashes, - std::vector architectures, - std::vector platformApplicabilityForPackage, - std::string fileMoniker, - std::unique_ptr& out) noexcept; - - AppFile(AppFile&&) noexcept; - - AppFile(const AppFile&) = delete; - AppFile& operator=(const AppFile&) = delete; - - /// Getter methods from File class - using File::GetFileId; - using File::GetHashes; - using File::GetSizeInBytes; - using File::GetUrl; - - /** - * @return Set of details related to applicability of the file - */ - const ApplicabilityDetails& GetApplicabilityDetails() const noexcept; - - /** - * @return Package Moniker of the file - */ - const std::string& GetFileMoniker() const noexcept; - - private: - AppFile(std::string&& fileId, - std::string&& url, - uint64_t sizeInBytes, - std::unordered_map&& hashes, - std::string&& fileMoniker); - - std::unique_ptr m_applicabilityDetails; - std::string m_fileMoniker; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/ApplicabilityDetails.h b/src/SfsClient/sfs-client/client/include/sfsclient/ApplicabilityDetails.h deleted file mode 100644 index 0cbf451c8c..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/ApplicabilityDetails.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" - -#include -#include -#include - -namespace SFS -{ -enum class Architecture -{ - None, - Amd64, - Arm, - Arm64, - x86, -}; - -class ApplicabilityDetails -{ - public: - [[nodiscard]] static Result Make(std::vector architectures, - std::vector platformApplicabilityForPackage, - std::unique_ptr& out) noexcept; - - ApplicabilityDetails(const ApplicabilityDetails&) = delete; - ApplicabilityDetails& operator=(const ApplicabilityDetails&) = delete; - - const std::vector& GetArchitectures() const noexcept; - const std::vector& GetPlatformApplicabilityForPackage() const noexcept; - - private: - ApplicabilityDetails() = default; - - std::vector m_architectures; - std::vector m_platformApplicabilityForPackage; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/ClientConfig.h b/src/SfsClient/sfs-client/client/include/sfsclient/ClientConfig.h deleted file mode 100644 index 38d2defa4e..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/ClientConfig.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Logging.h" - -#include -#include - -namespace SFS -{ -/// @brief Configurations to create an SFSClient instance -struct ClientConfig -{ - /// @brief The account ID of the SFS service is used to identify the caller (required) - std::string accountId; - - /// @brief The instance ID of the SFS service - std::optional instanceId; - - /// @brief The namespace of the SFS service - std::optional nameSpace; - - /** - * @brief A logging callback function that is called when the SFSClient logs a message - * @details This function returns logging information from the SFSClient. The caller is responsible for incoporating - * the received data into their logging system. The callback will be called in the same thread as the - * main flow, so make sure the callback does not block for too long so it doesn't delay operations. The - * LogData does not exist after the callback returns, so caller has to copy it if the data will be stored. - */ - std::optional logCallbackFn; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/Content.h b/src/SfsClient/sfs-client/client/include/sfsclient/Content.h deleted file mode 100644 index 512e1058c2..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/Content.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "ContentId.h" -#include "File.h" -#include "Result.h" - -#include -#include -#include - -namespace SFS -{ -class Content -{ - public: - /** - * @brief This Make() method should be used when the caller wants the @param files to be cloned - */ - [[nodiscard]] static Result Make(std::string contentNameSpace, - std::string contentName, - std::string contentVersion, - const std::vector& files, - std::unique_ptr& out) noexcept; - - /** - * @brief This Make() method should be used when the caller wants the @param files to be moved - */ - [[nodiscard]] static Result Make(std::string contentNameSpace, - std::string contentName, - std::string contentVersion, - std::vector&& files, - std::unique_ptr& out) noexcept; - - /** - * @brief This Make() method should be used when the caller wants the @param contentId and @param files to be moved - */ - [[nodiscard]] static Result Make(std::unique_ptr&& contentId, - std::vector&& files, - std::unique_ptr& out) noexcept; - - Content(Content&&) noexcept; - - Content(const Content&) = delete; - Content& operator=(const Content&) = delete; - - /** - * @return Unique content identifier - */ - const ContentId& GetContentId() const noexcept; - - const std::vector& GetFiles() const noexcept; - - private: - Content() = default; - - std::unique_ptr m_contentId; - std::vector m_files; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/ContentId.h b/src/SfsClient/sfs-client/client/include/sfsclient/ContentId.h deleted file mode 100644 index 903cf8dedd..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/ContentId.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" - -#include -#include - -namespace SFS -{ -class ContentId -{ - public: - [[nodiscard]] static Result Make(std::string nameSpace, - std::string name, - std::string version, - std::unique_ptr& out) noexcept; - - ContentId(ContentId&&) noexcept; - - ContentId(const ContentId&) = delete; - ContentId& operator=(const ContentId&) = delete; - - /** - * @return Content namespace - */ - const std::string& GetNameSpace() const noexcept; - - /** - * @return Content name - */ - const std::string& GetName() const noexcept; - - /** - * @return 4-part integer version. Each part can range from 0-65535 - */ - const std::string& GetVersion() const noexcept; - - private: - ContentId() = default; - - std::string m_nameSpace; - std::string m_name; - std::string m_version; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/File.h b/src/SfsClient/sfs-client/client/include/sfsclient/File.h deleted file mode 100644 index 160801a41f..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/File.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" - -#include -#include -#include -#include - -namespace SFS -{ -enum class HashType -{ - Sha1, - Sha256 -}; - -class File -{ - public: - [[nodiscard]] static Result Make(std::string fileId, - std::string url, - uint64_t sizeInBytes, - std::unordered_map hashes, - std::unique_ptr& out) noexcept; - - File(File&&) noexcept; - - File(const File&) = delete; - File& operator=(const File&) = delete; - - /** - * @return Unique file identifier within a content version - */ - const std::string& GetFileId() const noexcept; - - /** - * @return Download URL - */ - const std::string& GetUrl() const noexcept; - - /** - * @return File size in number of bytes - */ - uint64_t GetSizeInBytes() const noexcept; - - /** - * @return Dictionary of algorithm type to base64 encoded file hash string - */ - const std::unordered_map& GetHashes() const noexcept; - - protected: - File(std::string&& fileId, - std::string&& url, - uint64_t sizeInBytes, - std::unordered_map&& hashes); - - [[nodiscard]] Result Clone(std::unique_ptr& out) const noexcept; - - friend class Content; - - std::string m_fileId; - std::string m_url; - uint64_t m_sizeInBytes; - std::unordered_map m_hashes; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/Logging.h b/src/SfsClient/sfs-client/client/include/sfsclient/Logging.h deleted file mode 100644 index d8608e19e0..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/Logging.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include -#include - -namespace SFS -{ -enum class LogSeverity -{ - Info, - Warning, - Error, - Verbose, -}; - -struct LogData -{ - LogSeverity severity; - const char* message; - const char* file; - unsigned line; - const char* function; - std::chrono::time_point time; -}; - -using LoggingCallbackFn = std::function; - -std::string_view ToString(LogSeverity severity) noexcept; -}; // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h b/src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h deleted file mode 100644 index b2961f5c99..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include -#include -#include - -namespace SFS -{ -using TargetingAttributes = std::unordered_map; - -constexpr unsigned c_maxRetries = 3; - -struct ProductRequest -{ - /// @brief The name or GUID that uniquely represents the product in the service (required) - std::string product; - - /// @brief Key-value pair to filter the data retrieved from the service. Known from publishing (optional) - TargetingAttributes attributes; -}; - -/// @brief Configurations to perform a request to the SFS service -struct RequestParams -{ - /// @brief List of products to be retrieved from the server (required) - /// @note At the moment only a single product request is supported. Using a vector for future implementation of - /// batch requests - std::vector productRequests; - - /// @brief Base CorrelationVector to be used in the request for service telemetry stitching (optional) - /// @note If not provided, a new CorrelationVector will be generated - std::optional baseCV; - - /// @brief Proxy setting which can be used to establish connections with the server (optional) - /// @note The string can be a hostname or dotted numerical IP address. It can be suffixed with the port number - /// like :[port], and can be prefixed with [scheme]://. If not provided, no proxy will be used. - std::optional proxy; - - /// @brief Retry for a web request after a failed attempt. If true, client will retry up to c_maxRetries times - bool retryOnError{true}; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/Result.h b/src/SfsClient/sfs-client/client/include/sfsclient/Result.h deleted file mode 100644 index 3a6924952a..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/Result.h +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include -#include - -namespace SFS -{ -class Result -{ - public: - enum Code : uint32_t - { - Success = 0x00000000, - - // - // Failure codes - // - - // Generic errors start at 0x8000'0000 - InvalidArg = 0x8000'0001, - NotImpl = 0x8000'0002, - NotSet = 0x8000'0003, - OutOfMemory = 0x8000'0004, - Unexpected = 0x8000'0005, - - // Connection errors start at 0x8000'1000 - ConnectionSetupFailed = 0x8000'1000, - ConnectionUnexpectedError = 0x8000'1001, - ConnectionUrlSetupFailed = 0x8000'1002, - - // Http Errors start at 0x8000'2000 - // Generic Http errors - HttpTimeout = 0x8000'2000, - HttpUnexpected = 0x8000'2001, - - // Last 3 digits mapped to Http status codes - HttpBadRequest = 0x8000'2400, - HttpNotFound = 0x8000'2404, - HttpMethodNotAllowed = 0x8000'2405, - HttpTooManyRequests = 0x8000'2429, - HttpServiceNotAvailable = 0x8000'2503, - - // Service errors start at 0x8000'3000 - ServiceInvalidResponse = 0x8000'3000, - ServiceUnexpectedContentType = 0x8000'3001, - }; - - Result(Code code) noexcept; - Result(Code code, std::string message) noexcept; - - Code GetCode() const noexcept; - - /// @brief Returns the message associated with the result code. - /// @note "GetMsg" is used instead of "GetMessage" to avoid conflicts with Windows API. - const std::string& GetMsg() const noexcept; - - bool IsSuccess() const noexcept; - bool IsFailure() const noexcept; - - /** - * @brief Returns true if the result code represents a successful operation. - */ - operator bool() const noexcept; - - bool operator==(Code resultCode) const noexcept; - bool operator!=(Code resultCode) const noexcept; - - // Results should not be compared directly - bool operator==(Result) const = delete; - bool operator!=(Result) const = delete; - bool operator>(Result) const = delete; - bool operator>=(Result) const = delete; - bool operator<(Result) const = delete; - bool operator<=(Result) const = delete; - - private: - Code m_code; - std::string m_message; -}; - -std::string_view ToString(Result::Code code) noexcept; -} // namespace SFS - -std::ostream& operator<<(std::ostream& os, const SFS::Result& value); -std::ostream& operator<<(std::ostream& os, const SFS::Result::Code& value); diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/SFSClient.h b/src/SfsClient/sfs-client/client/include/sfsclient/SFSClient.h deleted file mode 100644 index b8b1a5b27f..0000000000 --- a/src/SfsClient/sfs-client/client/include/sfsclient/SFSClient.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "AppContent.h" -#include "ClientConfig.h" -#include "Content.h" -#include "Logging.h" -#include "RequestParams.h" -#include "Result.h" - -#include -#include -#include -#include - -namespace SFS -{ -namespace details -{ -class SFSClientInterface; -} - -class SFSClient -{ - public: - ~SFSClient() noexcept; - - SFSClient(const SFSClient&) = delete; - SFSClient& operator=(const SFSClient&) = delete; - - /** - * @brief Make a new SFSClient object - * @details An SFSClient object is used to make calls to the SFS service. The SFSClient object is initialized with - * a few parameters that are used to build the URL for the SFS service. The URL is built as follows: - * https://{accountId}.api.cdp.microsoft.com/api/v2/contents/{instanceId}/namespaces/{nameSpace} - * The instanceId and nameSpace are optionally set in @param config and have a default value if not provided. - * The accountId is required and must be set to a non-empty value. - * - * @param config Describes a set of startup configurations for the SFSClient - */ - [[nodiscard]] static Result Make(ClientConfig config, std::unique_ptr& out) noexcept; - - // - // API to retrieve download information from the SFS Service - // - - /** - * @brief Retrieve combined metadata & download URLs from the latest version of specified products - * @note At the moment only a single product request is supported - * @param requestParams Parameters that define this request - * @param contents A vector of Content that is populated with the result - */ - [[nodiscard]] Result GetLatestDownloadInfo(const RequestParams& requestParams, - std::vector& contents) const noexcept; - - /** - * @brief Retrieve combined metadata & download URLs from the latest version of specified apps - * @note At the moment only a single product request is supported - * @param requestParams Parameters that define this request - * @param contents A vector of AppContent that is populated with the result - */ - [[nodiscard]] Result GetLatestAppDownloadInfo(const RequestParams& requestParams, - std::vector& contents) const noexcept; - - /** - * @return The version of the SFSClient library - */ - static const char* GetVersion() noexcept; - - private: - /** - * @brief Construct a new SFSClient object - */ - SFSClient() noexcept; - - std::unique_ptr m_impl; -}; -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/src/AppContent.cpp b/src/SfsClient/sfs-client/client/src/AppContent.cpp deleted file mode 100644 index 6a7a80aae2..0000000000 --- a/src/SfsClient/sfs-client/client/src/AppContent.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "AppContent.h" - -#include "details/ErrorHandling.h" - -using namespace SFS; - -Result AppPrerequisiteContent::Make(std::unique_ptr&& contentId, - std::vector&& files, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new AppPrerequisiteContent()); - tmp->m_contentId = std::move(contentId); - tmp->m_files = std::move(files); - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -AppPrerequisiteContent::AppPrerequisiteContent(AppPrerequisiteContent&& other) noexcept -{ - m_contentId = std::move(other.m_contentId); - m_files = std::move(other.m_files); -} - -const ContentId& AppPrerequisiteContent::GetContentId() const noexcept -{ - return *m_contentId; -} - -const std::vector& AppPrerequisiteContent::GetFiles() const noexcept -{ - return m_files; -} - -Result AppContent::Make(std::unique_ptr&& contentId, - std::string updateId, - std::vector&& prerequisites, - std::vector&& files, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new AppContent()); - tmp->m_contentId = std::move(contentId); - tmp->m_updateId = std::move(updateId); - tmp->m_prerequisites = std::move(prerequisites); - tmp->m_files = std::move(files); - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -AppContent::AppContent(AppContent&& other) noexcept -{ - m_contentId = std::move(other.m_contentId); - m_updateId = std::move(other.m_updateId); - m_prerequisites = std::move(other.m_prerequisites); - m_files = std::move(other.m_files); -} - -const ContentId& AppContent::GetContentId() const noexcept -{ - return *m_contentId; -} - -const std::string& AppContent::GetUpdateId() const noexcept -{ - return m_updateId; -} - -const std::vector& AppContent::GetFiles() const noexcept -{ - return m_files; -} - -const std::vector& AppContent::GetPrerequisites() const noexcept -{ - return m_prerequisites; -} diff --git a/src/SfsClient/sfs-client/client/src/AppFile.cpp b/src/SfsClient/sfs-client/client/src/AppFile.cpp deleted file mode 100644 index 88ea2e1726..0000000000 --- a/src/SfsClient/sfs-client/client/src/AppFile.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "AppFile.h" - -#include "details/ErrorHandling.h" - -using namespace SFS; - -AppFile::AppFile(std::string&& fileId, - std::string&& url, - uint64_t sizeInBytes, - std::unordered_map&& hashes, - std::string&& fileMoniker) - : File(std::move(fileId), std::move(url), sizeInBytes, std::move(hashes)) - , m_fileMoniker(std::move(fileMoniker)) -{ -} - -Result AppFile::Make(std::string fileId, - std::string url, - uint64_t sizeInBytes, - std::unordered_map hashes, - std::vector architectures, - std::vector platformApplicabilityForPackage, - std::string fileMoniker, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr details; - RETURN_IF_FAILED( - ApplicabilityDetails::Make(std::move(architectures), std::move(platformApplicabilityForPackage), details)); - - std::unique_ptr tmp( - new AppFile(std::move(fileId), std::move(url), sizeInBytes, std::move(hashes), std::move(fileMoniker))); - tmp->m_applicabilityDetails = std::move(details); - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -AppFile::AppFile(AppFile&& other) noexcept : File(std::move(other)) -{ - m_applicabilityDetails = std::move(other.m_applicabilityDetails); - m_fileMoniker = std::move(other.m_fileMoniker); -} - -const ApplicabilityDetails& AppFile::GetApplicabilityDetails() const noexcept -{ - return *m_applicabilityDetails; -} - -const std::string& AppFile::GetFileMoniker() const noexcept -{ - return m_fileMoniker; -} diff --git a/src/SfsClient/sfs-client/client/src/ApplicabilityDetails.cpp b/src/SfsClient/sfs-client/client/src/ApplicabilityDetails.cpp deleted file mode 100644 index c3fcb97e96..0000000000 --- a/src/SfsClient/sfs-client/client/src/ApplicabilityDetails.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ApplicabilityDetails.h" - -#include "details/ErrorHandling.h" - -using namespace SFS; - -Result ApplicabilityDetails::Make(std::vector architectures, - std::vector platformApplicabilityForPackage, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new ApplicabilityDetails()); - tmp->m_architectures = std::move(architectures); - tmp->m_platformApplicabilityForPackage = std::move(platformApplicabilityForPackage); - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -const std::vector& ApplicabilityDetails::GetArchitectures() const noexcept -{ - return m_architectures; -} - -const std::vector& ApplicabilityDetails::GetPlatformApplicabilityForPackage() const noexcept -{ - return m_platformApplicabilityForPackage; -} diff --git a/src/SfsClient/sfs-client/client/src/Content.cpp b/src/SfsClient/sfs-client/client/src/Content.cpp deleted file mode 100644 index 6b44bb7a3c..0000000000 --- a/src/SfsClient/sfs-client/client/src/Content.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Content.h" - -#include "details/ErrorHandling.h" - -using namespace SFS; - -Result Content::Make(std::string contentNameSpace, - std::string contentName, - std::string contentVersion, - const std::vector& files, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new Content()); - RETURN_IF_FAILED(ContentId::Make(std::move(contentNameSpace), - std::move(contentName), - std::move(contentVersion), - tmp->m_contentId)); - - for (const auto& file : files) - { - std::unique_ptr clone; - RETURN_IF_FAILED(file.Clone(clone)); - tmp->m_files.push_back(std::move(*clone)); - } - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -Result Content::Make(std::string contentNameSpace, - std::string contentName, - std::string contentVersion, - std::vector&& files, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new Content()); - RETURN_IF_FAILED(ContentId::Make(std::move(contentNameSpace), - std::move(contentName), - std::move(contentVersion), - tmp->m_contentId)); - tmp->m_files = std::move(files); - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -Result Content::Make(std::unique_ptr&& contentId, - std::vector&& files, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new Content()); - tmp->m_contentId = std::move(contentId); - tmp->m_files = std::move(files); - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -Content::Content(Content&& other) noexcept -{ - m_contentId = std::move(other.m_contentId); - m_files = std::move(other.m_files); -} - -const ContentId& Content::GetContentId() const noexcept -{ - return *m_contentId; -} - -const std::vector& Content::GetFiles() const noexcept -{ - return m_files; -} diff --git a/src/SfsClient/sfs-client/client/src/ContentId.cpp b/src/SfsClient/sfs-client/client/src/ContentId.cpp deleted file mode 100644 index b6d616f857..0000000000 --- a/src/SfsClient/sfs-client/client/src/ContentId.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentId.h" - -#include "details/ErrorHandling.h" - -using namespace SFS; - -Result ContentId::Make(std::string nameSpace, - std::string name, - std::string version, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new ContentId()); - tmp->m_nameSpace = std::move(nameSpace); - tmp->m_name = std::move(name); - tmp->m_version = std::move(version); - - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -ContentId::ContentId(ContentId&& other) noexcept -{ - m_nameSpace = std::move(other.m_nameSpace); - m_name = std::move(other.m_name); - m_version = std::move(other.m_version); -} - -const std::string& ContentId::GetNameSpace() const noexcept -{ - return m_nameSpace; -} - -const std::string& ContentId::GetName() const noexcept -{ - return m_name; -} - -const std::string& ContentId::GetVersion() const noexcept -{ - return m_version; -} diff --git a/src/SfsClient/sfs-client/client/src/File.cpp b/src/SfsClient/sfs-client/client/src/File.cpp deleted file mode 100644 index 5f2773c0ce..0000000000 --- a/src/SfsClient/sfs-client/client/src/File.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "File.h" - -#include "details/ErrorHandling.h" - -using namespace SFS; - -File::File(std::string&& fileId, - std::string&& url, - uint64_t sizeInBytes, - std::unordered_map&& hashes) - : m_fileId(std::move(fileId)) - , m_url(std::move(url)) - , m_sizeInBytes(sizeInBytes) - , m_hashes(std::move(hashes)) -{ -} - -Result File::Make(std::string fileId, - std::string url, - uint64_t sizeInBytes, - std::unordered_map hashes, - std::unique_ptr& out) noexcept -try -{ - out.reset(); - - std::unique_ptr tmp(new File(std::move(fileId), std::move(url), sizeInBytes, std::move(hashes))); - out = std::move(tmp); - - return Result::Success; -} -SFS_CATCH_RETURN() - -Result File::Clone(std::unique_ptr& out) const noexcept -{ - return Make(m_fileId, m_url, m_sizeInBytes, m_hashes, out); -} - -File::File(File&& other) noexcept -{ - m_fileId = std::move(other.m_fileId); - m_url = std::move(other.m_url); - m_sizeInBytes = other.m_sizeInBytes; - m_hashes = std::move(other.m_hashes); -} - -const std::string& File::GetFileId() const noexcept -{ - return m_fileId; -} - -const std::string& File::GetUrl() const noexcept -{ - return m_url; -} - -uint64_t File::GetSizeInBytes() const noexcept -{ - return m_sizeInBytes; -} - -const std::unordered_map& File::GetHashes() const noexcept -{ - return m_hashes; -} diff --git a/src/SfsClient/sfs-client/client/src/Logging.cpp b/src/SfsClient/sfs-client/client/src/Logging.cpp deleted file mode 100644 index 35ee5f6368..0000000000 --- a/src/SfsClient/sfs-client/client/src/Logging.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Logging.h" - -using SFS::LogSeverity; - -std::string_view SFS::ToString(LogSeverity severity) noexcept -{ - switch (severity) - { - case LogSeverity::Info: - return "Info"; - case LogSeverity::Warning: - return "Warning"; - case LogSeverity::Error: - return "Error"; - case LogSeverity::Verbose: - return "Verbose"; - } - return ""; -} diff --git a/src/SfsClient/sfs-client/client/src/Result.cpp b/src/SfsClient/sfs-client/client/src/Result.cpp deleted file mode 100644 index c65d600916..0000000000 --- a/src/SfsClient/sfs-client/client/src/Result.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Result.h" - -#include - -using namespace SFS; - -Result::Result(Code code) noexcept : m_code(code) -{ -} - -Result::Result(Code code, std::string message) noexcept : Result(code) -{ - try - { - m_message = std::move(message); - } - catch (...) - { - // Ignore exception, leave message empty - } -} - -Result::Code Result::GetCode() const noexcept -{ - return m_code; -} - -const std::string& Result::GetMsg() const noexcept -{ - return m_message; -} - -bool Result::IsSuccess() const noexcept -{ - return GetCode() == Success; -} - -bool Result::IsFailure() const noexcept -{ - return !IsSuccess(); -} - -Result::operator bool() const noexcept -{ - return IsSuccess(); -} - -bool Result::operator==(Code resultCode) const noexcept -{ - return GetCode() == resultCode; -} - -bool Result::operator!=(Code resultCode) const noexcept -{ - return GetCode() != resultCode; -} - -std::string_view SFS::ToString(Result::Code code) noexcept -{ - switch (code) - { - case Result::Success: - return "Success"; - - // - // Failure codes - // - - // Generic errors - case Result::InvalidArg: - return "InvalidArg"; - case Result::NotImpl: - return "NotImpl"; - case Result::NotSet: - return "NotSet"; - case Result::OutOfMemory: - return "OutOfMemory"; - case Result::Unexpected: - return "Unexpected"; - - // Connection errors - case Result::ConnectionSetupFailed: - return "ConnectionSetupFailed"; - case Result::ConnectionUnexpectedError: - return "ConnectionUnexpectedError"; - case Result::ConnectionUrlSetupFailed: - return "ConnectionUrlSetupFailed"; - - // Http Errors - case Result::HttpTimeout: - return "HttpTimeout"; - case Result::HttpUnexpected: - return "HttpUnexpected"; - case Result::HttpBadRequest: - return "HttpBadRequest"; - case Result::HttpNotFound: - return "HttpNotFound"; - case Result::HttpMethodNotAllowed: - return "HttpMethodNotAllowed"; - case Result::HttpTooManyRequests: - return "HttpTooManyRequests"; - case Result::HttpServiceNotAvailable: - return "HttpServiceNotAvailable"; - - // Service errors - case Result::ServiceInvalidResponse: - return "ServiceInvalidResponse"; - case Result::ServiceUnexpectedContentType: - return "ServiceUnexpectedContentType"; - } - return ""; -} - -std::ostream& operator<<(std::ostream& os, const Result& result) -{ - os << ToString(result.GetCode()); - return os; -} - -std::ostream& operator<<(std::ostream& os, const Result::Code& code) -{ - os << ToString(code); - return os; -} diff --git a/src/SfsClient/sfs-client/client/src/SFSClient.cpp b/src/SfsClient/sfs-client/client/src/SFSClient.cpp deleted file mode 100644 index c0b62ba805..0000000000 --- a/src/SfsClient/sfs-client/client/src/SFSClient.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "SFSClient.h" - -#include "details/ErrorHandling.h" -#include "details/ReportingHandler.h" -#include "details/SFSClientImpl.h" -#include "details/connection/CurlConnectionManager.h" - -using namespace SFS; -using namespace SFS::details; - -// Defining the constructor and destructor here allows us to use a unique_ptr to SFSClientImpl in the header file -SFSClient::SFSClient() noexcept = default; -SFSClient::~SFSClient() noexcept = default; - -Result SFSClient::Make(ClientConfig config, std::unique_ptr& out) noexcept -try -{ - out.reset(); - std::unique_ptr tmp(new SFSClient()); - tmp->m_impl = std::make_unique>(std::move(config)); - out = std::move(tmp); - - LOG_INFO(out->m_impl->GetReportingHandler(), "SFSClient instance created successfully. Version: %s", GetVersion()); - - return Result::Success; -} -SFS_CATCH_RETURN() - -Result SFSClient::GetLatestDownloadInfo(const RequestParams& requestParams, - std::vector& contents) const noexcept -try -{ - contents = m_impl->GetLatestDownloadInfo(requestParams); - return Result::Success; -} -SFS_CATCH_RETURN() - -Result SFSClient::GetLatestAppDownloadInfo(const RequestParams& requestParams, - std::vector& contents) const noexcept -try -{ - contents = m_impl->GetLatestAppDownloadInfo(requestParams); - return Result::Success; -} -SFS_CATCH_RETURN() - -const char* SFSClient::GetVersion() noexcept -{ -#ifdef SFS_GIT_INFO - return SFS_VERSION " (" SFS_GIT_INFO ")"; -#else - return SFS_VERSION; -#endif -} diff --git a/src/SfsClient/sfs-client/client/src/details/ContentUtil.cpp b/src/SfsClient/sfs-client/client/src/details/ContentUtil.cpp deleted file mode 100644 index 3f6a175a92..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/ContentUtil.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentUtil.h" - -#include - -using namespace SFS; -using namespace SFS::details; - -bool contentutil::operator==(const ContentId& lhs, const ContentId& rhs) -{ - // String characters can be UTF-8 encoded, so we need to compare them in a case-sensitive manner. - return lhs.GetNameSpace() == rhs.GetNameSpace() && lhs.GetName() == rhs.GetName() && - lhs.GetVersion() == rhs.GetVersion(); -} - -bool contentutil::operator!=(const ContentId& lhs, const ContentId& rhs) -{ - return !(lhs == rhs); -} - -bool contentutil::operator==(const File& lhs, const File& rhs) -{ - // String characters can be UTF-8 encoded, so we need to compare them in a case-sensitive manner. - return lhs.GetFileId() == rhs.GetFileId() && lhs.GetUrl() == rhs.GetUrl() && - lhs.GetSizeInBytes() == rhs.GetSizeInBytes() && lhs.GetHashes() == rhs.GetHashes(); -} - -bool contentutil::operator!=(const File& lhs, const File& rhs) -{ - return !(lhs == rhs); -} - -bool contentutil::operator==(const ApplicabilityDetails& lhs, const ApplicabilityDetails& rhs) -{ - // String characters can be UTF-8 encoded, so we need to compare them in a case-sensitive manner. - return lhs.GetArchitectures() == rhs.GetArchitectures() && - lhs.GetPlatformApplicabilityForPackage() == rhs.GetPlatformApplicabilityForPackage(); -} - -bool contentutil::operator!=(const ApplicabilityDetails& lhs, const ApplicabilityDetails& rhs) -{ - return !(lhs == rhs); -} - -bool contentutil::operator==(const AppFile& lhs, const AppFile& rhs) -{ - // String characters can be UTF-8 encoded, so we need to compare them in a case-sensitive manner. - return lhs.GetFileId() == rhs.GetFileId() && lhs.GetUrl() == rhs.GetUrl() && - lhs.GetSizeInBytes() == rhs.GetSizeInBytes() && lhs.GetHashes() == rhs.GetHashes() && - lhs.GetApplicabilityDetails() == rhs.GetApplicabilityDetails() && - lhs.GetFileMoniker() == rhs.GetFileMoniker(); -} - -bool contentutil::operator!=(const AppFile& lhs, const AppFile& rhs) -{ - return !(lhs == rhs); -} - -bool contentutil::operator==(const Content& lhs, const Content& rhs) -{ - return lhs.GetContentId() == rhs.GetContentId() && - (std::is_permutation(lhs.GetFiles().begin(), - lhs.GetFiles().end(), - rhs.GetFiles().begin(), - rhs.GetFiles().end(), - [](const File& flhs, const File& frhs) { return flhs == frhs; })); -} - -bool contentutil::operator!=(const Content& lhs, const Content& rhs) -{ - return !(lhs == rhs); -} - -bool contentutil::operator==(const AppPrerequisiteContent& lhs, const AppPrerequisiteContent& rhs) -{ - auto areFilesEqual = [&lhs, &rhs]() { - return std::is_permutation(lhs.GetFiles().begin(), - lhs.GetFiles().end(), - rhs.GetFiles().begin(), - rhs.GetFiles().end(), - [](const AppFile& flhs, const AppFile& frhs) { return flhs == frhs; }); - }; - return lhs.GetContentId() == rhs.GetContentId() && areFilesEqual(); -} - -bool contentutil::operator!=(const AppPrerequisiteContent& lhs, const AppPrerequisiteContent& rhs) -{ - return !(lhs == rhs); -} - -bool contentutil::operator==(const AppContent& lhs, const AppContent& rhs) -{ - auto arePrerequisitesEqual = [&lhs, &rhs]() { - return std::equal( - lhs.GetPrerequisites().begin(), - lhs.GetPrerequisites().end(), - rhs.GetPrerequisites().begin(), - rhs.GetPrerequisites().end(), - [](const AppPrerequisiteContent& clhs, const AppPrerequisiteContent& crhs) { return clhs == crhs; }); - }; - auto areFilesEqual = [&lhs, &rhs]() { - return std::is_permutation(lhs.GetFiles().begin(), - lhs.GetFiles().end(), - rhs.GetFiles().begin(), - rhs.GetFiles().end(), - [](const AppFile& flhs, const AppFile& frhs) { return flhs == frhs; }); - }; - return lhs.GetContentId() == rhs.GetContentId() && lhs.GetUpdateId() == rhs.GetUpdateId() && - arePrerequisitesEqual() && areFilesEqual(); -} - -bool contentutil::operator!=(const AppContent& lhs, const AppContent& rhs) -{ - return !(lhs == rhs); -} diff --git a/src/SfsClient/sfs-client/client/src/details/ContentUtil.h b/src/SfsClient/sfs-client/client/src/details/ContentUtil.h deleted file mode 100644 index 64c36d6541..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/ContentUtil.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "AppContent.h" -#include "Content.h" - -namespace SFS::details -{ -class ReportingHandler; - -namespace contentutil -{ -// -// Comparison operators -// - -/// @brief Compares two ContentId objects for equality. The values of members are strictly compared. -bool operator==(const ContentId& lhs, const ContentId& rhs); - -/// @brief Compares two ContentId objects for inequality. The values of members are strictly compared. -bool operator!=(const ContentId& lhs, const ContentId& rhs); - -/// @brief Compares two File objects for equality. The values of members are strictly compared. -bool operator==(const File& lhs, const File& rhs); - -/// @brief Compares two File objects for inequality. The values of members are strictly compared. -bool operator!=(const File& lhs, const File& rhs); - -/// @brief Compares two ApplicabilityDetails objects for equality. The values of members are strictly compared. -bool operator==(const ApplicabilityDetails& lhs, const ApplicabilityDetails& rhs); - -/// @brief Compares two ApplicabilityDetails objects for inequality. The values of members are strictly compared. -bool operator!=(const ApplicabilityDetails& lhs, const ApplicabilityDetails& rhs); - -/// @brief Compares two AppFile objects for equality. The values of members are strictly compared. -bool operator==(const AppFile& lhs, const AppFile& rhs); - -/// @brief Compares two AppFile objects for inequality. The values of members are strictly compared. -bool operator!=(const AppFile& lhs, const AppFile& rhs); - -/// @brief Compares two Content objects for equality. The values of members are strictly compared. -bool operator==(const Content& lhs, const Content& rhs); - -/// @brief Compares two Content objects for inequality. The values of members are strictly compared. -bool operator!=(const Content& lhs, const Content& rhs); - -/// @brief Compares two AppPrerequisiteContent objects for equality. The values of members are strictly compared. -bool operator==(const AppPrerequisiteContent& lhs, const AppPrerequisiteContent& rhs); - -/// @brief Compares two AppPrerequisiteContent objects for inequality. The values of members are strictly compared. -bool operator!=(const AppPrerequisiteContent& lhs, const AppPrerequisiteContent& rhs); - -/// @brief Compares two AppContent objects for equality. The values of members are strictly compared. -bool operator==(const AppContent& lhs, const AppContent& rhs); - -/// @brief Compares two AppContent objects for inequality. The values of members are strictly compared. -bool operator!=(const AppContent& lhs, const AppContent& rhs); -} // namespace contentutil -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/CorrelationVector.cpp b/src/SfsClient/sfs-client/client/src/details/CorrelationVector.cpp deleted file mode 100644 index 5aae74ce33..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/CorrelationVector.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "CorrelationVector.h" -#include "ErrorHandling.h" - -#include - -#include - -using namespace SFS::details; -using namespace microsoft; - -CorrelationVector::CorrelationVector() : m_cv(std::make_unique()) -{ -} - -CorrelationVector::CorrelationVector(const std::string& cv, const ReportingHandler& handler) -{ - THROW_CODE_IF_LOG(InvalidArg, cv.empty(), handler, "cv must not be empty"); - - try - { - m_cv = std::make_unique(correlation_vector::extend(cv)); - } - catch (std::invalid_argument& e) - { - THROW_LOG(Result(Result::InvalidArg, "baseCV is not a valid correlation vector: " + std::string(e.what())), - handler); - } -} - -CorrelationVector::~CorrelationVector() = default; -CorrelationVector::CorrelationVector(CorrelationVector&&) = default; -CorrelationVector& CorrelationVector::operator=(CorrelationVector&&) = default; - -std::string CorrelationVector::IncrementAndGet() -{ - // Only increment after it's used at least once - if (m_isFirstUse) - { - m_isFirstUse = false; - } - else - { - Increment(); - } - - return m_cv->to_string(); -} - -void CorrelationVector::Increment() -{ - m_cv->increment(); -} diff --git a/src/SfsClient/sfs-client/client/src/details/CorrelationVector.h b/src/SfsClient/sfs-client/client/src/details/CorrelationVector.h deleted file mode 100644 index b5055c70df..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/CorrelationVector.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include - -namespace microsoft -{ -class correlation_vector; -} - -namespace SFS::details -{ -class ReportingHandler; - -class CorrelationVector -{ - public: - CorrelationVector(); - CorrelationVector(const std::string& cv, const ReportingHandler& handler); - - ~CorrelationVector(); - - CorrelationVector(CorrelationVector&&); - CorrelationVector& operator=(CorrelationVector&&); - - /** - * @brief Returns the current correlation vector and increments the internal state - */ - std::string IncrementAndGet(); - - private: - void Increment(); - - std::unique_ptr m_cv; - bool m_isFirstUse = true; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/Env.cpp b/src/SfsClient/sfs-client/client/src/details/Env.cpp deleted file mode 100644 index da04e74c3a..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/Env.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Env.h" - -#include "ErrorHandling.h" - -#include - -using namespace SFS::details; -using SFS::details::env::ScopedEnv; - -std::optional env::GetEnv(const std::string& varName) -{ - if (varName.empty()) - { - return std::nullopt; - } -#ifdef _WIN32 - size_t len; - char* buf; - if (_dupenv_s(&buf, &len, varName.c_str()) == 0 && buf != nullptr) - { - std::string result{buf}; - free(buf); - return result; - } -#else - if (const char* envValue = std::getenv(varName.c_str())) - { - return std::string(envValue); - } -#endif - // Return std::nullopt if the environment variable is not set or in case of failure - return std::nullopt; -} - -bool env::SetEnv(const std::string& varName, const std::string& value) -{ - if (varName.empty() || value.empty()) - { - return false; - } -#ifdef _WIN32 - return _putenv_s(varName.c_str(), value.c_str()) == 0; -#else - return setenv(varName.c_str(), value.c_str(), 1 /*overwrite*/) == 0; -#endif -} - -bool env::UnsetEnv(const std::string& varName) -{ - if (varName.empty()) - { - return false; - } -#ifdef _WIN32 - // On Windows, setting the value to an empty string is equivalent to unsetting the variable - return _putenv_s(varName.c_str(), "") == 0; -#else - return unsetenv(varName.c_str()) == 0; -#endif -} - -ScopedEnv::ScopedEnv(std::string varName, const std::string& value) : m_varName(std::move(varName)) -{ - m_oldValue = GetEnv(m_varName); - if (!SetEnv(m_varName, value)) - { - throw SFSException(Result::Unexpected, "Failed to set environment variable"); - } -} - -ScopedEnv::~ScopedEnv() -{ - if (m_oldValue) - { - SetEnv(m_varName, *m_oldValue); - } - else - { - UnsetEnv(m_varName); - } -} diff --git a/src/SfsClient/sfs-client/client/src/details/Env.h b/src/SfsClient/sfs-client/client/src/details/Env.h deleted file mode 100644 index b33487e895..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/Env.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include - -namespace SFS::details::env -{ -/** - * @brief Get the value of an environment variable. - * @details std::nullopt is returned if the environment variable is not set or in case of failure. - * The returned string is encoded in ASCII in Windows, not UTF-8. - */ -std::optional GetEnv(const std::string& varName); - -/** - * @brief Set the value of an environment variable. - * @return false in case of failure. - */ -bool SetEnv(const std::string& varName, const std::string& value); - -/** - * @brief Unset the value of an environment variable. - * @return false in case of failure. If the environment variable didn't exist, it still returns true. - */ -bool UnsetEnv(const std::string& varName); - -class ScopedEnv -{ - public: - ScopedEnv(std::string varName, const std::string& value); - ~ScopedEnv(); - - private: - std::string m_varName; - std::optional m_oldValue; -}; -} // namespace SFS::details::env diff --git a/src/SfsClient/sfs-client/client/src/details/ErrorHandling.cpp b/src/SfsClient/sfs-client/client/src/details/ErrorHandling.cpp deleted file mode 100644 index 6e465a21b1..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/ErrorHandling.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ErrorHandling.h" - -#include "ReportingHandler.h" - -using namespace SFS::details; - -void SFS::details::LogFailedResult(const SFS::Result& result, - const ReportingHandler& handler, - const char* file, - unsigned line) -{ - if (result.IsFailure()) - { - LOG_ERROR(handler, - "FAILED [%s] %s%s(%s:%u)", - std::string(ToString(result.GetCode())).c_str(), - result.GetMsg().c_str(), - result.GetMsg().empty() ? "" : " ", - file, - line); - } -} - -void SFS::details::LogIfFailed(const Result& result, const ReportingHandler& handler, const char* file, unsigned line) -{ - if (result.IsFailure()) - { - LogFailedResult(result, handler, file, line); - } -} - -void SFS::details::ThrowLog(Result result, const ReportingHandler& handler, const char* file, unsigned line) -{ - assert(result.IsFailure()); - LogFailedResult(result, handler, file, line); - throw SFSException(std::move(result)); -} - -void SFS::details::ThrowIfFailedLog(Result result, const ReportingHandler& handler, const char* file, unsigned line) -{ - if (result.IsFailure()) - { - LogFailedResult(result, handler, file, line); - throw SFSException(std::move(result)); - } -} - -void SFS::details::ThrowCodeIf(Result::Code code, bool condition, std::string message) -{ - if (condition) - { - Result result(code, std::move(message)); - assert(result.IsFailure()); - throw SFSException(std::move(result)); - } -} - -void SFS::details::ThrowCodeIfLog(Result::Code code, - bool condition, - const ReportingHandler& handler, - const char* file, - unsigned line, - std::string message) -{ - if (condition) - { - Result result(code, std::move(message)); - assert(result.IsFailure()); - LogFailedResult(result, handler, file, line); - throw SFSException(std::move(result)); - } -} diff --git a/src/SfsClient/sfs-client/client/src/details/ErrorHandling.h b/src/SfsClient/sfs-client/client/src/details/ErrorHandling.h deleted file mode 100644 index d01a905713..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/ErrorHandling.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" -#include "SFSException.h" - -#include - -#define SFS_CATCH_RETURN() \ - catch (const std::bad_alloc&) \ - { \ - return Result::OutOfMemory; \ - } \ - catch (const SFS::details::SFSException& e) \ - { \ - return e.GetResult(); \ - } \ - catch (const std::exception&) \ - { \ - return Result::Unexpected; \ - } \ - catch (...) \ - { \ - return Result::Unexpected; \ - } - -#define SFS_CATCH_LOG_RETHROW(handler) \ - catch (const SFS::details::SFSException& e) \ - { \ - SFS::details::LogFailedResult(e.GetResult(), handler, __FILE__, __LINE__); \ - throw; \ - } - -#define RETURN_IF_FAILED(result) \ - do \ - { \ - auto __result = (result); /* Assigning to a variable ensures a code block gets called only once */ \ - if (__result.IsFailure()) \ - { \ - return __result; \ - } \ - } while ((void)0, 0) - -#define RETURN_IF_FAILED_LOG(result, handler) \ - do \ - { \ - auto __result = (result); /* Assigning to a variable ensures a code block gets called only once */ \ - if (__result.IsFailure()) \ - { \ - SFS::details::LogFailedResult(__result, handler, __FILE__, __LINE__); \ - return __result; \ - } \ - } while ((void)0, 0) - -#define LOG_IF_FAILED(result, handler) LogIfFailed(result, handler, __FILE__, __LINE__) - -#define THROW_LOG(result, handler) ThrowLog(result, handler, __FILE__, __LINE__) - -#define THROW_IF_FAILED_LOG(result, handler) ThrowIfFailedLog(result, handler, __FILE__, __LINE__) - -#define THROW_CODE_IF(code, condition, ...) ThrowCodeIf(SFS::Result::code, condition, ##__VA_ARGS__) - -#define THROW_CODE_IF_LOG(code, condition, handler, ...) \ - ThrowCodeIfLog(SFS::Result::code, condition, handler, __FILE__, __LINE__, ##__VA_ARGS__) - -#define THROW_CODE_IF_NOT_LOG(code, condition, handler, ...) \ - ThrowCodeIfLog(SFS::Result::code, !(condition), handler, __FILE__, __LINE__, ##__VA_ARGS__) - -namespace SFS::details -{ -class ReportingHandler; - -void LogFailedResult(const Result& result, const ReportingHandler& handler, const char* file, unsigned line); -void LogIfFailed(const Result& result, const ReportingHandler& handler, const char* file, unsigned line); - -void ThrowLog(Result result, const ReportingHandler& handler, const char* file, unsigned line); -void ThrowIfFailedLog(Result result, const ReportingHandler& handler, const char* file, unsigned line); -void ThrowCodeIf(Result::Code code, bool condition, std::string message = {}); -void ThrowCodeIfLog(Result::Code code, - bool condition, - const ReportingHandler& handler, - const char* file, - unsigned line, - std::string message = {}); -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/OSInfo.cpp b/src/SfsClient/sfs-client/client/src/details/OSInfo.cpp deleted file mode 100644 index 9786cf55fb..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/OSInfo.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "OSInfo.h" - -#ifdef _WIN32 - -#include - -#include - -#elif __linux__ - -#include - -#endif - -using namespace SFS::details; - -namespace -{ -#ifdef _WIN32 -std::string GetPlatform() -{ - return "Windows"; -} - -std::string GetOSMachineInfo() -{ - SYSTEM_INFO systemInfo; - GetNativeSystemInfo(&systemInfo); - - switch (systemInfo.wProcessorArchitecture) - { - case PROCESSOR_ARCHITECTURE_AMD64: - return "x64"; - case PROCESSOR_ARCHITECTURE_ARM: - return "ARM"; - case PROCESSOR_ARCHITECTURE_ARM64: - return "ARM64"; - case PROCESSOR_ARCHITECTURE_INTEL: - return "x86"; - default: - return "Unknown"; - } -} - -#elif __linux__ - -std::string GetPlatform() -{ - // Return kernel name, usually "Linux" - utsname buf; - if (uname(&buf) >= 0) - { - return buf.sysname; - } - return "Unknown"; -} - -std::string GetOSMachineInfo() -{ - utsname buf; - if (uname(&buf) >= 0) - { - return buf.machine; - } - return "Unknown"; -} - -#else -#error "Unsupported platform" -#endif -} // namespace - -std::string osinfo::GetPlatform() -{ - return ::GetPlatform(); -} - -std::string osinfo::GetOSMachineInfo() -{ - return ::GetOSMachineInfo(); -} diff --git a/src/SfsClient/sfs-client/client/src/details/OSInfo.h b/src/SfsClient/sfs-client/client/src/details/OSInfo.h deleted file mode 100644 index 357af007e7..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/OSInfo.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include - -namespace SFS::details::osinfo -{ -std::string GetPlatform(); -std::string GetOSMachineInfo(); -} // namespace SFS::details::osinfo diff --git a/src/SfsClient/sfs-client/client/src/details/ReportingHandler.cpp b/src/SfsClient/sfs-client/client/src/details/ReportingHandler.cpp deleted file mode 100644 index 427817c2c2..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/ReportingHandler.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ReportingHandler.h" - -#include - -using namespace SFS; -using namespace SFS::details; - -void ReportingHandler::SetLoggingCallback(LoggingCallbackFn&& callback) -{ - std::lock_guard guard(m_loggingCallbackFnMutex); - m_loggingCallbackFn = std::move(callback); -} - -void ReportingHandler::CallLoggingCallback(LogSeverity severity, - const char* message, - const char* file, - unsigned line, - const char* function) const -{ - std::lock_guard guard(m_loggingCallbackFnMutex); - if (m_loggingCallbackFn) - { - m_loggingCallbackFn({severity, message, file, line, function, std::chrono::system_clock::now()}); - } -} diff --git a/src/SfsClient/sfs-client/client/src/details/ReportingHandler.h b/src/SfsClient/sfs-client/client/src/details/ReportingHandler.h deleted file mode 100644 index 44f5a5d1b8..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/ReportingHandler.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Logging.h" - -#include -#include - -#define MAX_LOG_MESSAGE_SIZE 1024 - -#define LOG_INFO(handler, format, ...) \ - handler.LogWithSeverity(SFS::LogSeverity::Info, __FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__) -#define LOG_WARNING(handler, format, ...) \ - handler.LogWithSeverity(SFS::LogSeverity::Warning, __FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__) -#define LOG_ERROR(handler, format, ...) \ - handler.LogWithSeverity(SFS::LogSeverity::Error, __FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__) -#define LOG_VERBOSE(handler, format, ...) \ - handler.LogWithSeverity(SFS::LogSeverity::Verbose, __FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__) - -namespace SFS::details -{ -/** - * @brief This class enables thread-safe access to the externally set logging callback function. - * @details Each SFSClient instance will have one ReportingHandler instance, and access to the logging callback function - * is controlled by a mutex that makes sure that only one thread can access the logging callback function at a time. - */ -class ReportingHandler -{ - public: - ReportingHandler() = default; - - ReportingHandler(const ReportingHandler&) = delete; - ReportingHandler& operator=(const ReportingHandler&) = delete; - - /** - * @brief Sets the logging callback function. - * @details This function is thread-safe. - * @param callback The logging callback function. To reset, pass a nullptr. - */ - void SetLoggingCallback(LoggingCallbackFn&& callback); - - /** - * @brief Logs a message with the given severity. - * @details Prefer calling it with macros LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_VERBOSE so file, line and function - * are automatically populated and the message can be formatted. - */ - template - void LogWithSeverity(LogSeverity severity, - const char* file, - unsigned line, - const char* function, - const char* format, - const Args&... args) const - { - constexpr std::size_t n = sizeof...(Args); - if constexpr (n == 0) - { - CallLoggingCallback(severity, format, file, line, function); - } - else - { - char message[MAX_LOG_MESSAGE_SIZE]; - snprintf(message, MAX_LOG_MESSAGE_SIZE, format, args...); - CallLoggingCallback(severity, message, file, line, function); - } - } - - private: - void CallLoggingCallback(LogSeverity severity, - const char* message, - const char* file, - unsigned line, - const char* function) const; - - LoggingCallbackFn m_loggingCallbackFn; - mutable std::mutex m_loggingCallbackFnMutex; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/SFSClientImpl.cpp b/src/SfsClient/sfs-client/client/src/details/SFSClientImpl.cpp deleted file mode 100644 index 87373e55cd..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/SFSClientImpl.cpp +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "SFSClientImpl.h" - -#include "AppContent.h" -#include "Content.h" -#include "ErrorHandling.h" -#include "Logging.h" -#include "TestOverride.h" -#include "Util.h" -#include "connection/Connection.h" -#include "connection/ConnectionManager.h" -#include "connection/CurlConnectionManager.h" -#include "connection/mock/MockConnectionManager.h" - -#include - -#include - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::util; -using json = nlohmann::json; - -constexpr const char* c_defaultInstanceId = "default"; -constexpr const char* c_defaultNameSpace = "default"; - -namespace -{ -void ValidateClientConfig(const ClientConfig& config, const ReportingHandler& handler) -{ - THROW_CODE_IF_LOG(InvalidArg, config.accountId.empty(), handler, "ClientConfig::accountId must not be empty"); - - if (config.instanceId) - { - THROW_CODE_IF_LOG(InvalidArg, - config.instanceId->empty(), - handler, - "ClientConfig::instanceId must not be empty"); - } - - if (config.nameSpace) - { - THROW_CODE_IF_LOG(InvalidArg, config.nameSpace->empty(), handler, "ClientConfig::nameSpace must not be empty"); - } -} - -void LogIfTestOverridesAllowed(const ReportingHandler& handler) -{ - if (test::AreTestOverridesAllowed()) - { - LOG_INFO(handler, "Test overrides are allowed"); - } -} - -void ThrowInvalidResponseIfFalse(bool condition, const std::string& message, const ReportingHandler& handler) -{ - THROW_CODE_IF_LOG(ServiceInvalidResponse, !condition, handler, message); -} - -json ParseServerMethodStringToJson(const std::string& data, const std::string& method, const ReportingHandler& handler) -{ - try - { - return json::parse(data); - } - catch (json::parse_error& ex) - { - THROW_LOG( - Result(Result::ServiceInvalidResponse, "(" + method + ") JSON Parsing error: " + std::string(ex.what())), - handler); - return json(); // Unreachable code, but the compiler doesn't know that. - } -} - -VersionEntities ConvertLatestVersionBatchResponseToVersionEntities(const json& data, const ReportingHandler& handler) -{ - // Expected format: - // [ - // { - // "ContentId": { - // "Namespace": , - // "Name": , - // "Version": - // } - // }, - // ... - // ] - // - - ThrowInvalidResponseIfFalse(data.is_array(), "Response is not a JSON array", handler); - ThrowInvalidResponseIfFalse(data.size() > 0, "Response does not have the expected size", handler); - - VersionEntities entities; - for (const auto& obj : data) - { - entities.push_back(std::move(VersionEntity::FromJson(obj, handler))); - } - - return entities; -} - -bool VerifyVersionResponseMatchesProduct(const ContentIdEntity& contentId, - std::string_view nameSpace, - std::string_view name) -{ - return contentId.nameSpace == nameSpace && contentId.name == name; -} - -void ValidateVersionEntity(const VersionEntity& versionEntity, - const std::string& nameSpace, - const std::string& product, - const ReportingHandler& handler) -{ - THROW_CODE_IF_NOT_LOG(ServiceInvalidResponse, - VerifyVersionResponseMatchesProduct(versionEntity.contentId, nameSpace, product), - handler, - "Response does not match the requested product"); -} - -void ValidateBatchVersionEntity(const VersionEntities& versionEntities, - const std::string& nameSpace, - const std::unordered_set& requestedProducts, - const ReportingHandler& handler) -{ - for (const auto& entity : versionEntities) - { - THROW_CODE_IF_LOG(ServiceInvalidResponse, - requestedProducts.count(entity->contentId.name) == 0, - handler, - "Received product [" + entity->contentId.name + - "] which is not one of the requested products"); - THROW_CODE_IF_LOG(ServiceInvalidResponse, - AreNotEqualI(entity->contentId.nameSpace, nameSpace), - handler, - "Received product [" + entity->contentId.name + "] with a namespace [" + - entity->contentId.nameSpace + "] that does not match the requested namespace"); - - LOG_INFO(handler, - "Received a response for product [%s] with version %s", - entity->contentId.name.c_str(), - entity->contentId.version.c_str()); - } -} - -void ValidateRequestParams(const RequestParams& requestParams, const ReportingHandler& handler) -{ - THROW_CODE_IF_LOG(InvalidArg, requestParams.productRequests.empty(), handler, "productRequests cannot be empty"); - - // TODO #78: Add support for multiple product requests - THROW_CODE_IF_LOG(NotImpl, - requestParams.productRequests.size() > 1, - handler, - "There cannot be more than 1 productRequest at the moment"); - - for (const auto& [product, _] : requestParams.productRequests) - { - THROW_CODE_IF_LOG(InvalidArg, product.empty(), handler, "product must not be empty"); - } -} -} // namespace - -template -SFSClientImpl::SFSClientImpl(ClientConfig&& config) -{ - if (config.logCallbackFn) - { - m_reportingHandler.SetLoggingCallback(std::move(*config.logCallbackFn)); - } - - ValidateClientConfig(config, m_reportingHandler); - - m_accountId = std::move(config.accountId); - m_instanceId = - (config.instanceId && !config.instanceId->empty()) ? std::move(*config.instanceId) : c_defaultInstanceId; - m_nameSpace = (config.nameSpace && !config.nameSpace->empty()) ? std::move(*config.nameSpace) : c_defaultNameSpace; - - static_assert(std::is_base_of::value, - "ConnectionManagerT not derived from ConnectionManager"); - m_connectionManager = std::make_unique(m_reportingHandler); - - LogIfTestOverridesAllowed(m_reportingHandler); -} - -template -std::unique_ptr SFSClientImpl::GetLatestVersion(const ProductRequest& productRequest, - Connection& connection) const -try -{ - const auto& [product, attributes] = productRequest; - const std::string url{MakeUrlBuilder().GetLatestVersionUrl(product)}; - - LOG_INFO(m_reportingHandler, "Requesting latest version of [%s] from URL [%s]", product.c_str(), url.c_str()); - - const json body = {{"TargetingAttributes", attributes}}; - LOG_VERBOSE(m_reportingHandler, "Request body [%s]", body.dump().c_str()); - - const std::string postResponse{connection.Post(url, body.dump())}; - const json versionResponse = ParseServerMethodStringToJson(postResponse, "GetLatestVersion", m_reportingHandler); - - auto versionEntity = VersionEntity::FromJson(versionResponse, m_reportingHandler); - ValidateVersionEntity(*versionEntity, m_nameSpace, product, m_reportingHandler); - - LOG_INFO(m_reportingHandler, "Received a response with version %s", versionEntity->contentId.version.c_str()); - - return versionEntity; -} -SFS_CATCH_LOG_RETHROW(m_reportingHandler) - -template -VersionEntities SFSClientImpl::GetLatestVersionBatch( - const std::vector& productRequests, - Connection& connection) const -try -{ - const std::string url{MakeUrlBuilder().GetLatestVersionBatchUrl()}; - - LOG_INFO(m_reportingHandler, "Requesting latest version of multiple products from URL [%s]", url.c_str()); - - // Creating request body - std::unordered_set requestedProducts; - json body = json::array(); - for (const auto& [product, attributes] : productRequests) - { - LOG_INFO(m_reportingHandler, "Product #%zu: [%s]", body.size() + size_t{1}, product.c_str()); - requestedProducts.insert(product); - - body.push_back({{"TargetingAttributes", attributes}, {"Product", product}}); - } - - LOG_VERBOSE(m_reportingHandler, "Request body [%s]", body.dump().c_str()); - - const std::string postResponse{connection.Post(url, body.dump())}; - - const json versionResponse = - ParseServerMethodStringToJson(postResponse, "GetLatestVersionBatch", m_reportingHandler); - - auto entities = ConvertLatestVersionBatchResponseToVersionEntities(versionResponse, m_reportingHandler); - ValidateBatchVersionEntity(entities, m_nameSpace, requestedProducts, m_reportingHandler); - - return entities; -} -SFS_CATCH_LOG_RETHROW(m_reportingHandler) - -template -std::unique_ptr SFSClientImpl::GetSpecificVersion(const std::string& product, - const std::string& version, - Connection& connection) const -try -{ - const std::string url{MakeUrlBuilder().GetSpecificVersionUrl(product, version)}; - - LOG_INFO(m_reportingHandler, - "Requesting version [%s] of [%s] from URL [%s]", - version.c_str(), - product.c_str(), - url.c_str()); - - const std::string getResponse{connection.Get(url)}; - - const json versionResponse = ParseServerMethodStringToJson(getResponse, "GetSpecificVersion", m_reportingHandler); - - auto versionEntity = VersionEntity::FromJson(versionResponse, m_reportingHandler); - ValidateVersionEntity(*versionEntity, m_nameSpace, product, m_reportingHandler); - - LOG_INFO(m_reportingHandler, - "Received the expected response with version %s", - versionEntity->contentId.version.c_str()); - - return versionEntity; -} -SFS_CATCH_LOG_RETHROW(m_reportingHandler) - -template -FileEntities SFSClientImpl::GetDownloadInfo(const std::string& product, - const std::string& version, - Connection& connection) const -try -{ - const std::string url{MakeUrlBuilder().GetDownloadInfoUrl(product, version)}; - - LOG_INFO(m_reportingHandler, - "Requesting download info of version [%s] of [%s] from URL [%s]", - version.c_str(), - product.c_str(), - url.c_str()); - - const std::string postResponse{connection.Post(url)}; - - const json downloadInfoResponse = - ParseServerMethodStringToJson(postResponse, "GetDownloadInfo", m_reportingHandler); - - auto files = FileEntity::DownloadInfoResponseToFileEntities(downloadInfoResponse, m_reportingHandler); - - LOG_INFO(m_reportingHandler, "Received a response with %zu files", files.size()); - - return files; -} -SFS_CATCH_LOG_RETHROW(m_reportingHandler) - -template -std::vector SFSClientImpl::GetLatestDownloadInfo(const RequestParams& requestParams) const -try -{ - ValidateRequestParams(requestParams, m_reportingHandler); - - const auto connection = MakeConnection(ConnectionConfig(requestParams)); - - auto versionEntity = GetLatestVersion(requestParams.productRequests[0], *connection); - auto contentId = VersionEntity::ToContentId(std::move(*versionEntity), m_reportingHandler); - - const auto& product = requestParams.productRequests[0].product; - auto fileEntities = GetDownloadInfo(product, contentId->GetVersion(), *connection); - auto files = GenericFileEntity::FileEntitiesToFileVector(std::move(fileEntities), m_reportingHandler); - - std::unique_ptr content; - THROW_IF_FAILED_LOG(Content::Make(std::move(contentId), std::move(files), content), m_reportingHandler); - - std::vector contents; - contents.push_back(std::move(*content)); - - return contents; -} -SFS_CATCH_LOG_RETHROW(m_reportingHandler) - -template -std::vector SFSClientImpl::GetLatestAppDownloadInfo( - const RequestParams& requestParams) const -try -{ - ValidateRequestParams(requestParams, m_reportingHandler); - - // TODO #150: For now apps are only coming from the "storeapps" instanceId and the service has requested - // we double check for it. In the future we should remove this check and allow the user to specify any instanceId - THROW_CODE_IF_LOG(Unexpected, - AreNotEqualI(m_instanceId, "storeapps"), - m_reportingHandler, - "At this moment only the \"storeapps\" instanceId can send app requests"); - - const auto connection = MakeConnection(ConnectionConfig(requestParams)); - - auto versionEntity = GetLatestVersion(requestParams.productRequests[0], *connection); - - auto appVersionEntity = AppVersionEntity::GetAppVersionEntityPtr(versionEntity, m_reportingHandler); - auto contentId = AppVersionEntity::ToContentId(std::move(*appVersionEntity), m_reportingHandler); - - LOG_INFO(m_reportingHandler, "Getting download info for main app content"); - const auto& product = requestParams.productRequests[0].product; - auto fileEntities = GetDownloadInfo(product, contentId->GetVersion(), *connection); - auto files = AppFileEntity::FileEntitiesToAppFileVector(std::move(fileEntities), m_reportingHandler); - - std::vector prerequisites; - for (auto& prereq : appVersionEntity->prerequisites) - { - LOG_INFO(m_reportingHandler, "Getting download info for prerequisite [%s]", prereq.contentId.name.c_str()); - auto prereqContentId = GenericVersionEntity::ToContentId(std::move(prereq), m_reportingHandler); - - auto prereqFileEntities = - GetDownloadInfo(prereqContentId->GetName(), prereqContentId->GetVersion(), *connection); - auto prereqFiles = - AppFileEntity::FileEntitiesToAppFileVector(std::move(prereqFileEntities), m_reportingHandler); - - std::unique_ptr prereqContent; - THROW_IF_FAILED_LOG( - AppPrerequisiteContent::Make(std::move(prereqContentId), std::move(prereqFiles), prereqContent), - m_reportingHandler); - - prerequisites.push_back(std::move(*prereqContent)); - } - - std::unique_ptr content; - THROW_IF_FAILED_LOG(AppContent::Make(std::move(contentId), - std::move(appVersionEntity->updateId), - std::move(prerequisites), - std::move(files), - content), - m_reportingHandler); - - std::vector contents; - contents.push_back(std::move(*content)); - - return contents; -} -SFS_CATCH_LOG_RETHROW(m_reportingHandler) - -template -std::unique_ptr SFSClientImpl::MakeConnection(const ConnectionConfig& config) const -{ - return m_connectionManager->MakeConnection(config); -} - -template -void SFSClientImpl::SetCustomBaseUrl(std::string customBaseUrl) -{ - m_customBaseUrl = std::move(customBaseUrl); -} - -template -SFSUrlBuilder SFSClientImpl::MakeUrlBuilder() const -{ - if (auto envVar = test::GetTestOverride(test::TestOverride::BaseUrl)) - { - return SFSUrlBuilder(SFSCustomUrl(*envVar), m_instanceId, m_nameSpace, m_reportingHandler); - } - if (m_customBaseUrl) - { - return SFSUrlBuilder(SFSCustomUrl(*m_customBaseUrl), m_instanceId, m_nameSpace, m_reportingHandler); - } - - return SFSUrlBuilder(m_accountId, m_instanceId, m_nameSpace, m_reportingHandler); -} - -template class SFS::details::SFSClientImpl; -template class SFS::details::SFSClientImpl; diff --git a/src/SfsClient/sfs-client/client/src/details/SFSClientImpl.h b/src/SfsClient/sfs-client/client/src/details/SFSClientImpl.h deleted file mode 100644 index 016c5f48e6..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/SFSClientImpl.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "SFSClientInterface.h" - -#include "ClientConfig.h" -#include "Content.h" -#include "Logging.h" -#include "Result.h" -#include "SFSUrlBuilder.h" - -#include -#include -#include - -namespace SFS::details -{ -template -class SFSClientImpl : public SFSClientInterface -{ - public: - SFSClientImpl(ClientConfig&& config); - ~SFSClientImpl() override = default; - - // - // Combined API calls for retrieval of metadata & download URLs - // - - /** - * @brief Retrieve combined metadata & download URLs from the latest version of specified products - * @note At the moment only a single product request is supported - * @param requestParams Parameters that define this request - */ - std::vector GetLatestDownloadInfo(const RequestParams& requestParams) const override; - - /** - * @brief Retrieve combined metadata & download URLs from the latest version of specified apps - * @note At the moment only a single product request is supported - * @param requestParams Parameters that define this request - */ - std::vector GetLatestAppDownloadInfo(const RequestParams& requestParams) const override; - - // - // Individual APIs 1:1 with service endpoints (SFSClientInterface) - // - - /** - * @brief Gets the metadata for the latest available version for the specified product request - * @return Entity that describes the latest version of the product - * @throws SFSException if the request fails - */ - std::unique_ptr GetLatestVersion(const ProductRequest& productRequest, - Connection& connection) const override; - - /** - * @brief Gets the metadata for the latest available version for the specified product requests - * @return Vector of entities that describe the latest version of the products - * @throws SFSException if the request fails - */ - VersionEntities GetLatestVersionBatch(const std::vector& productRequests, - Connection& connection) const override; - - /** - * @brief Gets the metadata for a specific version of the specified product - * @return Entity that describes the latest version of the product - * @throws SFSException if the request fails - */ - std::unique_ptr GetSpecificVersion(const std::string& product, - const std::string& version, - Connection& connection) const override; - - /** - * @brief Gets the files metadata for a specific version of the specified product - * @return Vector of File entities for the specific version of the product - * @throws SFSException if the request fails - */ - FileEntities GetDownloadInfo(const std::string& product, - const std::string& version, - Connection& connection) const override; - - /** - * @brief Returns a new Connection to be used by the SFSClient to make requests - * @param config Configurations for the connection object - */ - std::unique_ptr MakeConnection(const ConnectionConfig& config) const override; - - // - // Configuration methods - // - - /** - * @brief Allows one to override the base URL used to make calls to the SFS service - * @details Not exposed to the user. Used for testing purposes only - * @param customBaseUrl The custom base URL to use - */ - void SetCustomBaseUrl(std::string customBaseUrl); - - /** - * @return A SFSUrlBuilder object that can be used to build URLs for the SFS service - */ - SFSUrlBuilder MakeUrlBuilder() const; - - private: - std::string m_accountId; - std::string m_instanceId; - std::string m_nameSpace; - - std::unique_ptr m_connectionManager; - - std::optional m_customBaseUrl; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/SFSClientInterface.h b/src/SfsClient/sfs-client/client/src/details/SFSClientInterface.h deleted file mode 100644 index 3381f34ac4..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/SFSClientInterface.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Logging.h" -#include "ReportingHandler.h" -#include "RequestParams.h" -#include "entity/FileEntity.h" -#include "entity/VersionEntity.h" - -#include -#include - -namespace SFS -{ -class AppContent; -class Content; - -namespace details -{ -class Connection; -class ConnectionManager; -struct ConnectionConfig; - -class SFSClientInterface -{ - public: - virtual ~SFSClientInterface() - { - } - - // - // Combined API calls for retrieval of metadata & download URLs - // - - /** - * @brief Retrieve combined metadata & download URLs from the latest version of specified products - * @note At the moment only a single product request is supported - * @param requestParams Parameters that define this request - */ - virtual std::vector GetLatestDownloadInfo(const RequestParams& requestParams) const = 0; - - /** - * @brief Retrieve combined metadata & download URLs from the latest version of specified apps - * @note At the moment only a single product request is supported - * @param requestParams Parameters that define this request - */ - virtual std::vector GetLatestAppDownloadInfo(const RequestParams& requestParams) const = 0; - - // - // Individual APIs 1:1 with service endpoints - // - - /** - * @brief Gets the metadata for the latest available version for the specified product request - * @return Entity that describes the latest version of the product - * @throws SFSException if the request fails - */ - virtual std::unique_ptr GetLatestVersion(const ProductRequest& productRequest, - Connection& connection) const = 0; - - /** - * @brief Gets the metadata for the latest available version for the specified product requests - * @return Vector of entities that describe the latest version of the products - * @throws SFSException if the request fails - */ - virtual VersionEntities GetLatestVersionBatch(const std::vector& productRequests, - Connection& connection) const = 0; - - /** - * @brief Gets the metadata for a specific version of the specified product - * @return Entity that describes the latest version of the product - * @throws SFSException if the request fails - */ - virtual std::unique_ptr GetSpecificVersion(const std::string& product, - const std::string& version, - Connection& connection) const = 0; - - /** - * @brief Gets the files metadata for a specific version of the specified product - * @return Vector of File entities for the specific version of the product - * @throws SFSException if the request fails - */ - virtual FileEntities GetDownloadInfo(const std::string& product, - const std::string& version, - Connection& connection) const = 0; - - /** - * @brief Returns a new Connection to be used by the SFSClient to make requests - * @param config Configurations for the connection object - */ - virtual std::unique_ptr MakeConnection(const ConnectionConfig& config) const = 0; - - const ReportingHandler& GetReportingHandler() const - { - return m_reportingHandler; - } - - protected: - ReportingHandler m_reportingHandler; -}; -} // namespace details -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/src/details/SFSException.cpp b/src/SfsClient/sfs-client/client/src/details/SFSException.cpp deleted file mode 100644 index 2f5bf1261c..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/SFSException.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "SFSException.h" - -using namespace SFS::details; - -SFSException::SFSException(SFS::Result result) : m_result(std::move(result)) -{ -} - -SFSException::SFSException(SFS::Result::Code code) : m_result(Result(code)) -{ -} - -SFSException::SFSException(SFS::Result::Code code, std::string message) : m_result(Result(code, std::move(message))) -{ -} - -const SFS::Result& SFSException::GetResult() const noexcept -{ - return m_result; -} - -const char* SFSException::what() const noexcept -{ - return m_result.GetMsg().c_str(); -} diff --git a/src/SfsClient/sfs-client/client/src/details/SFSException.h b/src/SfsClient/sfs-client/client/src/details/SFSException.h deleted file mode 100644 index 0d281c5263..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/SFSException.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" - -#include -#include - -namespace SFS::details -{ -class SFSException : public std::exception -{ - public: - SFSException() = default; - explicit SFSException(SFS::Result result); - - explicit SFSException(SFS::Result::Code code); - SFSException(SFS::Result::Code code, std::string message); - - const SFS::Result& GetResult() const noexcept; - const char* what() const noexcept override; - - private: - SFS::Result m_result; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.cpp b/src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.cpp deleted file mode 100644 index 592adc3f07..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "SFSUrlBuilder.h" - -using namespace SFS::details; - -constexpr const char* c_apiVersion = "v2"; -constexpr const char* c_apiDomain = "api.cdp.microsoft.com"; - -SFSUrlBuilder::SFSUrlBuilder(const std::string& accountId, - std::string instanceId, - std::string nameSpace, - const ReportingHandler& handler) - : UrlBuilder(handler) - , m_instanceId(std::move(instanceId)) - , m_nameSpace(std::move(nameSpace)) -{ - SetScheme(Scheme::Https); - SetHost(accountId + "." + std::string(c_apiDomain)); -} - -SFSUrlBuilder::SFSUrlBuilder(const SFSCustomUrl& customUrl, - std::string instanceId, - std::string nameSpace, - const ReportingHandler& handler) - : UrlBuilder(handler) - , m_instanceId(std::move(instanceId)) - , m_nameSpace(std::move(nameSpace)) -{ - SetUrl(customUrl.url); -} - -std::string SFSUrlBuilder::GetLatestVersionUrl(const std::string& product) -{ - SetVersionsUrlPath(product); - AppendPath("latest"); - SetQuery("action", "select"); - return GetUrl(); -} - -std::string SFSUrlBuilder::GetLatestVersionBatchUrl() -{ - SetNamesUrlPath(); - SetQuery("action", "BatchUpdates"); - return GetUrl(); -} - -std::string SFSUrlBuilder::GetSpecificVersionUrl(const std::string& product, const std::string& version) -{ - SetVersionsUrlPath(product); - AppendPathEncoded(version); - return GetUrl(); -} - -std::string SFSUrlBuilder::GetDownloadInfoUrl(const std::string& product, const std::string& version) -{ - SetVersionsUrlPath(product); - AppendPathEncoded(version); - AppendPath("files"); - SetQuery("action", "GenerateDownloadInfo"); - return GetUrl(); -} - -SFSUrlBuilder& SFSUrlBuilder::SetNamesUrlPath() -{ - ResetPath().ResetQuery(); - AppendPath("api").AppendPath(c_apiVersion); - AppendPath("contents").AppendPathEncoded(m_instanceId); - AppendPath("namespaces").AppendPathEncoded(m_nameSpace); - AppendPath("names"); - return *this; -} - -SFSUrlBuilder& SFSUrlBuilder::SetVersionsUrlPath(const std::string& product) -{ - SetNamesUrlPath(); - AppendPathEncoded(product); - AppendPath("versions"); - return *this; -} diff --git a/src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.h b/src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.h deleted file mode 100644 index 0c57b55177..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/SFSUrlBuilder.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "UrlBuilder.h" - -namespace SFS::details -{ -// Wrapper struct that only exists to allow the SFSUrlBuilder to have a constructor that takes a custom URL -struct SFSCustomUrl -{ - explicit SFSCustomUrl(std::string url) : url(std::move(url)) - { - } - - std::string url; -}; - -class SFSUrlBuilder : private UrlBuilder -{ - public: - SFSUrlBuilder(const std::string& accountId, - std::string instanceId, - std::string nameSpace, - const ReportingHandler& handler); - - SFSUrlBuilder(const SFSCustomUrl& customUrl, - std::string instanceId, - std::string nameSpace, - const ReportingHandler& handler); - - std::string GetLatestVersionUrl(const std::string& product); - std::string GetLatestVersionBatchUrl(); - std::string GetSpecificVersionUrl(const std::string& product, const std::string& version); - std::string GetDownloadInfoUrl(const std::string& product, const std::string& version); - - using UrlBuilder::GetUrl; - - private: - SFSUrlBuilder& SetNamesUrlPath(); - SFSUrlBuilder& SetVersionsUrlPath(const std::string& product); - - std::string m_instanceId; - std::string m_nameSpace; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/TestOverride.cpp b/src/SfsClient/sfs-client/client/src/details/TestOverride.cpp deleted file mode 100644 index 0ada93a231..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/TestOverride.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "TestOverride.h" - -using namespace SFS; -using SFS::test::ScopedTestOverride; -using SFS::test::TestOverride; - -bool test::AreTestOverridesAllowed() -{ -#ifdef SFS_ENABLE_TEST_OVERRIDES - return true; -#else - return false; -#endif -} - -std::string test::GetEnvVarNameFromOverride(TestOverride override) -{ - switch (override) - { - case TestOverride::BaseRetryDelayMs: - return "SFS_TEST_BASE_RETRY_DELAY_MS"; - case TestOverride::BaseUrl: - return "SFS_TEST_OVERRIDE_BASE_URL"; - } - return ""; -} - -std::optional test::GetTestOverride(TestOverride override) -{ - if (!AreTestOverridesAllowed()) - { - return std::nullopt; - } - - return details::env::GetEnv(GetEnvVarNameFromOverride(override)); -} - -std::optional test::GetTestOverrideAsInt(TestOverride override) -{ - auto str = GetTestOverride(override); - if (str) - { - return std::stoi(*str); - } - return std::nullopt; -} - -ScopedTestOverride::ScopedTestOverride(TestOverride override, const std::string& value) - : m_scopedEnv(GetEnvVarNameFromOverride(override), value) -{ -} - -ScopedTestOverride::ScopedTestOverride(TestOverride override, int value) - : m_scopedEnv(GetEnvVarNameFromOverride(override), std::to_string(value)) -{ -} diff --git a/src/SfsClient/sfs-client/client/src/details/TestOverride.h b/src/SfsClient/sfs-client/client/src/details/TestOverride.h deleted file mode 100644 index 87dd76d08d..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/TestOverride.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Env.h" - -#include -#include - -namespace SFS::test -{ -/** - * @brief Check if test overrides are allowed and logs if so. - * @details Test overrides are allowed if the SFS_ENABLE_TEST_OVERRIDES macro is defined. - */ -bool AreTestOverridesAllowed(); - -enum class TestOverride -{ - BaseRetryDelayMs, // Integer. Allows one to override the base retry delay. - BaseUrl, // String. Allows one to override the base URL used for all requests -}; - -/** - * @brief Get the environment variable name for a given test override - */ -std::string GetEnvVarNameFromOverride(TestOverride override); - -/** - * @brief Get the value of a test override - * @details std::nullopt is returned if the environment variable is not set or in case of failure. - * The returned string may be different in Win32 due to the encoding of the environment variables. - */ -std::optional GetTestOverride(TestOverride override); - -/** - * @brief Get the value of a test override as int - * @details std::nullopt is returned if the environment variable is not set or in case of failure. - */ -std::optional GetTestOverrideAsInt(TestOverride override); - -class ScopedTestOverride -{ - public: - ScopedTestOverride(TestOverride override, const std::string& value); - ScopedTestOverride(TestOverride override, int value); - - private: - SFS::details::env::ScopedEnv m_scopedEnv; -}; -} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp b/src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp deleted file mode 100644 index 95d2abd8f5..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "UrlBuilder.h" - -#include "ErrorHandling.h" -#include "ReportingHandler.h" - -#include - -#include - -#define THROW_IF_CURL_URL_ERROR(curlCall, error) \ - do \ - { \ - auto __curlUrlCode = (curlCall); \ - THROW_CODE_IF_NOT_LOG(error, __curlUrlCode == CURLUE_OK, m_handler, GetCurlUrlStrError(__curlUrlCode)); \ - } while ((void)0, 0) - -#define THROW_IF_CURL_URL_SETUP_ERROR(curlCall) THROW_IF_CURL_URL_ERROR(curlCall, ConnectionUrlSetupFailed) - -using namespace SFS::details; - -namespace -{ -struct CurlCharDeleter -{ - void operator()(char* val) - { - if (val) - { - curl_free(val); - } - } -}; - -using CurlCharPtr = std::unique_ptr; - -std::string GetCurlUrlStrError(CURLUcode code) -{ - return "Curl URL error: " + std::string(curl_url_strerror(code)); -} -} // namespace - -UrlBuilder::UrlBuilder(const ReportingHandler& handler) : m_handler(handler) -{ - m_handle = curl_url(); - THROW_CODE_IF_NOT_LOG(ConnectionUrlSetupFailed, m_handle, m_handler, "Curl URL error: Failed to create URL"); -} - -UrlBuilder::UrlBuilder(const std::string& url, const ReportingHandler& handler) : UrlBuilder(handler) -{ - SetUrl(url); -} - -UrlBuilder::~UrlBuilder() -{ - curl_url_cleanup(m_handle); -} - -std::string UrlBuilder::GetUrl() const -{ - CurlCharPtr url; - char* urlPtr = url.get(); - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_get(m_handle, CURLUPART_URL, &urlPtr, 0 /*flags*/)); - return urlPtr; -} - -std::string UrlBuilder::GetPath() const -{ - CurlCharPtr path; - char* pathPtr = path.get(); - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_get(m_handle, CURLUPART_PATH, &pathPtr, 0 /*flags*/)); - return pathPtr; -} - -std::string UrlBuilder::GetQuery() const -{ - CurlCharPtr query; - char* queryPtr = query.get(); - const auto queryResult = curl_url_get(m_handle, CURLUPART_QUERY, &queryPtr, 0 /*flags*/); - switch (queryResult) - { - case CURLUE_OK: - return queryPtr; - case CURLUE_NO_QUERY: - return {}; - default: - THROW_IF_CURL_URL_SETUP_ERROR(queryResult); - } - return {}; -} - -UrlBuilder& UrlBuilder::SetScheme(Scheme scheme) -{ - switch (scheme) - { - case Scheme::Https: - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_SCHEME, "https", 0 /*flags*/)); - break; - } - return *this; -} - -UrlBuilder& UrlBuilder::SetHost(const std::string& host) -{ - THROW_CODE_IF_LOG(InvalidArg, host.empty(), m_handler, "Host must not empty"); - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_HOST, host.c_str(), 0 /*flags*/)); - return *this; -} - -UrlBuilder& UrlBuilder::SetPath(const std::string& path) -{ - THROW_CODE_IF_LOG(InvalidArg, path.empty(), m_handler, "Path must not empty"); - m_path = path; - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_PATH, m_path.c_str(), 0 /*flags*/)); - return *this; -} - -UrlBuilder& UrlBuilder::AppendPath(const std::string& path) -{ - return AppendPath(path, false /*encode*/); -} - -UrlBuilder& UrlBuilder::AppendPathEncoded(const std::string& path) -{ - return AppendPath(path, true /*encode*/); -} - -UrlBuilder& UrlBuilder::AppendPath(const std::string& path, bool encode) -{ - THROW_CODE_IF_LOG(InvalidArg, path.empty(), m_handler, "Path must not empty"); - if (!m_path.empty() && m_path.back() != '/') - { - m_path += '/'; - } - - if (encode) - { - m_path += URLEncode(path); - } - else - { - m_path += path; - } - - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_PATH, m_path.c_str(), 0 /*flags*/)); - return *this; -} - -UrlBuilder& UrlBuilder::ResetPath() -{ - m_path.clear(); - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_PATH, "", 0 /*flags*/)); - return *this; -} - -UrlBuilder& UrlBuilder::SetQuery(const std::string& key, const std::string& value) -{ - THROW_CODE_IF_LOG(InvalidArg, key.empty() || value.empty(), m_handler, "Query key and value must not empty"); - const std::string query = URLEncode(key) + "=" + URLEncode(value); - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_QUERY, query.c_str(), 0 /*flags*/)); - return *this; -} - -UrlBuilder& UrlBuilder::AppendQuery(const std::string& key, const std::string& value) -{ - THROW_CODE_IF_LOG(InvalidArg, key.empty() || value.empty(), m_handler, "Query key and value must not empty"); - const std::string query = URLEncode(key) + "=" + URLEncode(value); - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_QUERY, query.c_str(), CURLU_APPENDQUERY)); - return *this; -} - -UrlBuilder& UrlBuilder::ResetQuery() -{ - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_QUERY, "", 0 /*flags*/)); - return *this; -} - -UrlBuilder& UrlBuilder::SetUrl(const std::string& url) -{ - THROW_CODE_IF_LOG(InvalidArg, url.empty(), m_handler, "Url must not empty"); - THROW_IF_CURL_URL_SETUP_ERROR(curl_url_set(m_handle, CURLUPART_URL, url.c_str(), 0 /*flags*/)); - return *this; -} - -std::string UrlBuilder::URLEncode(const std::string& str) const -{ - CurlCharPtr encodedStr{curl_easy_escape(nullptr /*ignored*/, str.c_str(), static_cast(str.length()))}; - THROW_CODE_IF_NOT_LOG(ConnectionUrlSetupFailed, encodedStr, m_handler, "Failed to URL-encode string"); - return encodedStr.get(); -} diff --git a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.h b/src/SfsClient/sfs-client/client/src/details/UrlBuilder.h deleted file mode 100644 index cf738dab1e..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.h +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include - -// Forward declaration -struct Curl_URL; -typedef struct Curl_URL CURLU; - -namespace SFS::details -{ -class ReportingHandler; - -enum class Scheme -{ - Https, -}; - -class UrlBuilder -{ - public: - /** - * @brief Construct a new Url Builder object with an empty URL - */ - explicit UrlBuilder(const ReportingHandler& handler); - - /** - * @brief Construct a new Url Builder object with an existing URL - * @param url The URL to set - */ - UrlBuilder(const std::string& url, const ReportingHandler& handler); - - ~UrlBuilder(); - - UrlBuilder(const UrlBuilder&) = delete; - UrlBuilder& operator=(const UrlBuilder&) = delete; - - std::string GetUrl() const; - - std::string GetPath() const; - std::string GetQuery() const; - - /** - * @brief Set the scheme for the URL - * @param scheme The scheme to set for the URL Ex: Https - * @return The reference to the current object - */ - UrlBuilder& SetScheme(Scheme scheme); - - /** - * @brief Set the host for the URL - * @param host The host to set for the URL. Ex: www.example.com - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& SetHost(const std::string& host); - - /** - * @brief Set a path to the URL - * @param path The path to set for the URL. Ex: index.html - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& SetPath(const std::string& path); - - /** - * @brief Append a path to the URL - * @param path The path to be appended for the URL. Ex: index.html - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& AppendPath(const std::string& path); - - /** - * @brief Append a path to the URL. The new path element will be URL encoded, including forward slashes - * @param path The path to be appended for the URL. Ex: index.html - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& AppendPathEncoded(const std::string& path); - - /** - * @brief Reset the path of the URL - * @return The reference to the current object - */ - UrlBuilder& ResetPath(); - - /** - * @brief Set a query to the URL (?key=value) - * @param key The key of the query string. Ex: value - * @param value The value of the query string. Ex: value - * @note Both key and value will be URL encoded - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& SetQuery(const std::string& key, const std::string& value); - - /** - * @brief Append a query to the URL (&key=value) - * @param key The key of the query string. Ex: value - * @param value The value of the query string. Ex: value - * @note Both key and value will be URL encoded - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& AppendQuery(const std::string& key, const std::string& value); - - /** - * @brief Reset the query of the URL - * @return The reference to the current object - */ - UrlBuilder& ResetQuery(); - - /** - * @brief Set the URL through a string. Other methods can still be called later to modify the URl - * @param url The string to set as URL. Ex: http://www.example.com/index.html - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& SetUrl(const std::string& url); - - private: - /** - * @brief Append a path to the URL - * @param path The path to be appended for the URL. Ex: index.html - * @param encode If true, the new path element will be URL encoded, including forward slashes - * @throws SFSException if the string is invalid - * @return The reference to the current object - */ - UrlBuilder& AppendPath(const std::string& path, bool encode); - - /** - * @brief URL-encode (or percent-encode) a given string - */ - std::string URLEncode(const std::string& str) const; - - const ReportingHandler& m_handler; - - CURLU* m_handle = nullptr; - std::string m_path; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/Util.cpp b/src/SfsClient/sfs-client/client/src/details/Util.cpp deleted file mode 100644 index 8912505544..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/Util.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Util.h" - -#include -#include - -using namespace SFS::details; - -bool util::AreEqualI(std::string_view a, std::string_view b) -{ - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { - return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); - }); -} - -bool util::AreNotEqualI(std::string_view a, std::string_view b) -{ - return !AreEqualI(a, b); -} diff --git a/src/SfsClient/sfs-client/client/src/details/Util.h b/src/SfsClient/sfs-client/client/src/details/Util.h deleted file mode 100644 index 8e7545ba09..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/Util.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include - -namespace SFS::details::util -{ -bool AreEqualI(std::string_view a, std::string_view b); -bool AreNotEqualI(std::string_view a, std::string_view b); -} // namespace SFS::details::util diff --git a/src/SfsClient/sfs-client/client/src/details/connection/Connection.cpp b/src/SfsClient/sfs-client/client/src/details/connection/Connection.cpp deleted file mode 100644 index 6669fa11b9..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/Connection.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Connection.h" - -using namespace SFS; -using namespace SFS::details; - -Connection::Connection(const ConnectionConfig& config, const ReportingHandler& handler) : m_handler(handler) -{ - if (config.baseCV) - { - m_cv = std::move(CorrelationVector(*config.baseCV, m_handler)); - } - m_maxRetries = config.maxRetries; -} - -std::string Connection::Post(const std::string& url) -{ - return Post(url, {}); -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/Connection.h b/src/SfsClient/sfs-client/client/src/details/connection/Connection.h deleted file mode 100644 index 0fc1589345..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/Connection.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "../CorrelationVector.h" -#include "ConnectionConfig.h" - -#include - -namespace SFS::details -{ -class ReportingHandler; - -class Connection -{ - public: - Connection(const ConnectionConfig& config, const ReportingHandler& handler); - - virtual ~Connection() - { - } - - Connection(const Connection&) = delete; - Connection& operator=(const Connection&) = delete; - - /** - * @brief Perform a GET request to the given @param url - * @return The response body - * @throws SFSException if the request fails - */ - virtual std::string Get(const std::string& url) = 0; - - /** - * @brief Perform a POST request to the given @param url with @param data as the request body - * @return The response body - * @throws SFSException if the request fails - */ - virtual std::string Post(const std::string& url, const std::string& data) = 0; - - /** - * @brief Perform a POST request to the given @param url - * @return The response body - * @throws SFSException if the request fails - */ - std::string Post(const std::string& url); - - protected: - const ReportingHandler& m_handler; - - /// @brief The correlation vector to use for requests - CorrelationVector m_cv; - - /// @brief Expected number of retries for a web request after a failed attempt - unsigned m_maxRetries{3}; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp deleted file mode 100644 index 319d5a132c..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ConnectionConfig.h" - -#include "RequestParams.h" - -using namespace SFS; -using namespace SFS::details; - -ConnectionConfig::ConnectionConfig(const SFS::RequestParams& requestParams) - : maxRetries(requestParams.retryOnError ? c_maxRetries : 0) - , baseCV(requestParams.baseCV) - , proxy(requestParams.proxy) -{ -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h deleted file mode 100644 index 30d963ae9f..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include - -namespace SFS -{ -struct RequestParams; - -namespace details -{ -struct ConnectionConfig -{ - ConnectionConfig() = default; - explicit ConnectionConfig(const RequestParams& requestParams); - - /// @brief Expected number of retries for a web request after a failed attempt - unsigned maxRetries{3}; - - /// @brief The correlation vector to use for requests - std::optional baseCV; - - /// @brief Proxy setting which can be used to establish connections with the server - std::optional proxy; -}; -} // namespace details -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.cpp b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.cpp deleted file mode 100644 index 1c711a9d83..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ConnectionManager.h" - -#include "../ReportingHandler.h" - -using namespace SFS::details; - -ConnectionManager::ConnectionManager(const ReportingHandler& handler) : m_handler(handler) -{ -} - -ConnectionManager::~ConnectionManager() -{ -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.h b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.h deleted file mode 100644 index 82d32d9b55..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionManager.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include - -namespace SFS::details -{ -class Connection; -class ReportingHandler; -struct ConnectionConfig; - -class ConnectionManager -{ - public: - ConnectionManager(const ReportingHandler& handler); - virtual ~ConnectionManager(); - - ConnectionManager(const ConnectionManager&) = delete; - ConnectionManager& operator=(const ConnectionManager&) = delete; - - virtual std::unique_ptr MakeConnection(const ConnectionConfig& config) = 0; - - protected: - const ReportingHandler& m_handler; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp b/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp deleted file mode 100644 index c7c0683141..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "CurlConnection.h" - -#include "../ErrorHandling.h" -#include "../ReportingHandler.h" -#include "../TestOverride.h" -#include "HttpHeader.h" - -#include - -#include -#include -#include -#include - -#define THROW_IF_CURL_ERROR(curlCall, error) \ - do \ - { \ - auto __curlCode = (curlCall); \ - std::string __message = "Curl error: " + std::string(curl_easy_strerror(__curlCode)); \ - THROW_CODE_IF_NOT_LOG(error, __curlCode == CURLE_OK, m_handler, std::move(__message)); \ - } while ((void)0, 0) - -#define THROW_IF_CURL_SETUP_ERROR(curlCall) THROW_IF_CURL_ERROR(curlCall, ConnectionSetupFailed) -#define THROW_IF_CURL_UNEXPECTED_ERROR(curlCall) THROW_IF_CURL_ERROR(curlCall, ConnectionUnexpectedError) - -// Setting a hard limit of 100k characters for the response to avoid rogue servers sending huge amounts of data -#define MAX_RESPONSE_CHARACTERS 100000 - -using namespace SFS; -using namespace SFS::details; -using namespace std::chrono_literals; - -namespace -{ -// Curl callback for writing data to a std::string. Must return the number of bytes written. -// This callback may be called multiple times for a single request, and will keep appending -// to userData until the request is complete. The data received is not null-terminated. -// For SFS, this data will likely be a JSON string. -size_t WriteCallback(char* contents, size_t sizeInBytes, size_t numElements, void* userData) -{ - auto readBufferPtr = static_cast(userData); - if (readBufferPtr) - { - size_t totalSize = sizeInBytes * numElements; - - // Checking final response size to avoid unexpected amounts of data - if ((readBufferPtr->length() + totalSize) > MAX_RESPONSE_CHARACTERS) - { - return CURL_WRITEFUNC_ERROR; - } - - readBufferPtr->append(contents, totalSize); - return totalSize; - } - return CURL_WRITEFUNC_ERROR; -} - -struct CurlErrorBuffer -{ - public: - CurlErrorBuffer(CURL* handle, const ReportingHandler& reportingHandler) - : m_handle(handle) - , m_reportingHandler(reportingHandler) - { - m_errorBuffer[0] = '\0'; - SetBuffer(); - } - - ~CurlErrorBuffer() - { - LOG_IF_FAILED(UnsetBuffer(), m_reportingHandler); - } - - void SetBuffer() - { - THROW_CODE_IF_NOT_LOG(ConnectionSetupFailed, - curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_errorBuffer) == CURLE_OK, - m_reportingHandler, - "Failed to set up error buffer for curl"); - } - - Result UnsetBuffer() - { - return curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, nullptr) == CURLE_OK - ? Result::Success - : Result(Result::ConnectionSetupFailed, "Failed to unset curl error buffer"); - } - - char* Get() - { - return m_errorBuffer; - } - - private: - CURL* m_handle; - const ReportingHandler& m_reportingHandler; - - char m_errorBuffer[CURL_ERROR_SIZE]; -}; - -Result CurlCodeToResult(CURLcode curlCode, char* errorBuffer) -{ - Result::Code code; - switch (curlCode) - { - case CURLE_OPERATION_TIMEDOUT: - code = Result::HttpTimeout; - break; - default: - code = Result::ConnectionUnexpectedError; - break; - } - - const bool isErrorStringRegistered = errorBuffer && errorBuffer[0] != '\0'; - std::string message = isErrorStringRegistered ? errorBuffer : "Curl error"; - - return Result(code, std::move(message)); -} - -bool IsSuccessfulSFSHttpCode(long httpCode) -{ - return httpCode == 200; -} - -Result HttpCodeToResult(long httpCode) -{ - if (IsSuccessfulSFSHttpCode(httpCode)) - { - return Result::Success; - } - - switch (httpCode) - { - case 400: - { - return Result(Result::HttpBadRequest, "400 Bad Request"); - } - case 404: - { - return Result(Result::HttpNotFound, "404 Not Found"); - } - case 405: - { - return Result(Result::HttpMethodNotAllowed, "405 Method Not Allowed"); - } - case 429: - { - return Result(Result::HttpTooManyRequests, "429 Too Many Requests"); - } - case 503: - { - return Result(Result::HttpServiceNotAvailable, "503 Service Unavailable"); - } - default: - { - return Result(Result::HttpUnexpected, "Unexpected HTTP code " + std::to_string(httpCode)); - } - } -} - -bool IsRetriableHttpError(long httpCode) -{ - switch (httpCode) - { - case 429: // Too Many Requests - Rate Limiting - case 500: // InternalServerError - Can be triggered within server timeouts, network issue - case 502: // BadGateway - Likely an issue with routing - case 503: // ServerBusy - case 504: // GatewayTimeout - return true; - default: - return false; - } -} - -std::optional GetResponseHeader(CURL* handle, - HttpHeader httpHeader, - const ReportingHandler& reportingHandler) -{ - const std::string headerName = ToString(httpHeader); - - // This struct only represents data inside the CURL handle, and must not be manually freed - curl_header* header; - const int lastRequest = -1; - CURLHcode curlhCode = curl_easy_header(handle, headerName.c_str(), 0 /*index*/, CURLH_HEADER, lastRequest, &header); - switch (curlhCode) - { - case CURLHE_OK: - return header->value; - case CURLHE_BADINDEX: - case CURLHE_MISSING: - case CURLHE_NOHEADERS: - return std::nullopt; - default: - THROW_LOG( - Result(Result::ConnectionUnexpectedError, - "Failed to get response header " + headerName + " with CURLH code " + std::to_string(curlhCode)), - reportingHandler); - } - return std::nullopt; -} - -std::chrono::milliseconds ParseRetryAfterValue(const std::string& retryAfter, const ReportingHandler& reportingHandler) -{ - LOG_VERBOSE(reportingHandler, "Parsing Retry-After value [%s]", retryAfter.c_str()); - std::chrono::seconds retryAfterSec{0}; - try - { - retryAfterSec = std::chrono::seconds(std::stoi(retryAfter)); - } - catch (std::invalid_argument&) - { - // Value is not an integer, but may still be in HTTP Date format - const time_t retryAfterSecSinceEpoch = curl_getdate(retryAfter.c_str(), nullptr /*unused*/); - if (retryAfterSecSinceEpoch == -1) - { - THROW_LOG(Result(Result::ConnectionUnexpectedError, - "Retry-After header value could not be converted to an integer or an HTTP Date"), - reportingHandler); - } - - // Get number of seconds since epoch for now to calculate the difference - const auto epoch = std::chrono::system_clock::now().time_since_epoch(); - const auto nowSecSinceEpoch = std::chrono::duration_cast(epoch); - - retryAfterSec = std::chrono::seconds(retryAfterSecSinceEpoch) - nowSecSinceEpoch; - } - catch (std::out_of_range&) - { - THROW_LOG(Result(Result::ConnectionUnexpectedError, "Retry-After header value is not in the expected range"), - reportingHandler); - } - if (retryAfterSec <= 0s) - { - THROW_LOG(Result(Result::ConnectionUnexpectedError, "Invalid Retry-After header value"), reportingHandler); - } - return retryAfterSec; -} -} // namespace - -namespace SFS::details -{ -struct CurlHeaderList -{ - public: - CurlHeaderList() = default; - - ~CurlHeaderList() - { - curl_slist_free_all(m_slist); - } - - /** - * @throws SFSException if the header cannot be added to the list. - */ - void Add(HttpHeader header, const std::string& value) - { - const std::string data = ToString(header) + ": " + value; - const auto ret = curl_slist_append(m_slist, data.c_str()); - if (!ret) - { - throw SFSException(Result::ConnectionSetupFailed, "Failed to add header " + data + " to CurlHeaderList"); - } - m_slist = ret; - } - - struct curl_slist* m_slist{nullptr}; -}; -} // namespace SFS::details - -CurlConnection::CurlConnection(const ConnectionConfig& config, const ReportingHandler& handler) - : Connection(config, handler) -{ - m_handle = curl_easy_init(); - THROW_CODE_IF_NOT_LOG(ConnectionSetupFailed, m_handle, m_handler, "Failed to init curl connection"); - - // Turning timeout signals off to avoid issues with threads - // See https://curl.se/libcurl/c/threadsafe.html - THROW_CODE_IF_NOT_LOG(ConnectionSetupFailed, - curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1L) == CURLE_OK, - m_handler, - "Failed to set up curl"); - - if (config.proxy) - { - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_PROXY, config.proxy->c_str())); - } - - // TODO #41: Pass AAD token in the header if it is available - // TODO #42: Cert pinning with service -} - -CurlConnection::~CurlConnection() -{ - if (m_handle) - { - curl_easy_cleanup(m_handle); - } -} - -std::string CurlConnection::Get(const std::string& url) -{ - THROW_CODE_IF_LOG(InvalidArg, url.empty(), m_handler, "url cannot be empty"); - - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_HTTPGET, 1L)); - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_HTTPHEADER, nullptr)); - - CurlHeaderList headers; - return CurlPerform(url, headers); -} - -std::string CurlConnection::Post(const std::string& url, const std::string& data) -{ - THROW_CODE_IF_LOG(InvalidArg, url.empty(), m_handler, "url cannot be empty"); - - CurlHeaderList headerList; - headerList.Add(HttpHeader::ContentType, "application/json"); - - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_POST, 1L)); - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_COPYPOSTFIELDS, data.c_str())); - - CurlHeaderList headers; - headers.Add(HttpHeader::ContentType, "application/json"); - return CurlPerform(url, headers); -} - -std::string CurlConnection::CurlPerform(const std::string& url, CurlHeaderList& headers) -{ - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_URL, url.c_str())); - - const std::string cv = m_cv.IncrementAndGet(); - headers.Add(HttpHeader::MSCV, cv); - headers.Add(HttpHeader::UserAgent, GetUserAgentValue()); - - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_HTTPHEADER, headers.m_slist)); - - // Setting up error buffer where error messages get written - this gets unset in the destructor - CurlErrorBuffer errorBuffer(m_handle, m_handler); - - std::string readBuffer; - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, WriteCallback)); - THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, &readBuffer)); - - // Retry the connection a specified number of times - const unsigned totalAttempts = 1 + m_maxRetries; - for (unsigned i = 0; i < totalAttempts; i++) - { - const unsigned attempt = i + 1; - LOG_INFO(m_handler, "Request attempt %u out of %u (cv: %s)", attempt, totalAttempts, cv.c_str()); - const bool lastAttempt = attempt == totalAttempts; - - // Clear the buffer before each attempt - readBuffer.clear(); - - // Perform the request - const auto result = curl_easy_perform(m_handle); - if (result != CURLE_OK) - { - THROW_LOG(CurlCodeToResult(result, errorBuffer.Get()), m_handler); - } - - // Check request status to stop or retry - long httpCode = 0; - THROW_IF_CURL_UNEXPECTED_ERROR(curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &httpCode)); - - if (IsSuccessfulSFSHttpCode(httpCode)) - { - break; - } - - const Result httpResult = HttpCodeToResult(httpCode); - if (!CanRetryRequest(lastAttempt, httpCode)) - { - THROW_LOG(httpResult, m_handler); - } - - ProcessRetry(attempt, httpResult); - } - - return readBuffer; -} - -bool CurlConnection::CanRetryRequest(bool lastAttempt, long httpCode) -{ - if (lastAttempt) - { - LOG_INFO(m_handler, "No retry as this is the last attempt"); - return false; - } - - if (!IsRetriableHttpError(httpCode)) - { - LOG_INFO(m_handler, "Error %ld is not retriable, stopping", httpCode); - return false; - } - - return true; -} - -void CurlConnection::ProcessRetry(int attempt, const Result& httpResult) -{ - // Wait before retrying. Prefer the Retry-After information if available - std::chrono::milliseconds retryDelay{0}; - const std::optional retryAfter = GetResponseHeader(m_handle, HttpHeader::RetryAfter, m_handler); - if (retryAfter) - { - // TODO #93: Enforce Retry-After value across calls to avoid caller spamming server - retryDelay = ParseRetryAfterValue(*retryAfter, m_handler); - } - else - { - // Apply exponential back-off with a factor of 2 - static std::chrono::milliseconds s_baseRetryDelay = 15s; // Value recommended as interval by the service - - std::chrono::milliseconds baseRetryDelay = s_baseRetryDelay; - - // Value can be overriden in tests - if (auto override = test::GetTestOverrideAsInt(test::TestOverride::BaseRetryDelayMs)) - { - baseRetryDelay = std::chrono::milliseconds{*override}; - } - - retryDelay = baseRetryDelay * (1 << (attempt - 1)); - } - - LOG_IF_FAILED(httpResult, m_handler); - LOG_INFO(m_handler, "Sleeping for %lld ms", static_cast(retryDelay.count())); - std::this_thread::sleep_for(retryDelay); -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.h b/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.h deleted file mode 100644 index 618ea2d6b9..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Connection.h" - -#include - -// Forward declaration -typedef void CURL; - -namespace SFS -{ -class Result; - -namespace details -{ -struct CurlHeaderList; -class ReportingHandler; - -class CurlConnection : public Connection -{ - public: - CurlConnection(const ConnectionConfig& config, const ReportingHandler& handler); - ~CurlConnection() override; - - /** - * @brief Perform a GET request to the given @param url - * @return The response body - * @throws SFSException if the request fails - */ - std::string Get(const std::string& url) override; - - /** - * @brief Perform a POST request to the given @param url with @param data as the request body - * @return The response body - * @throws SFSException if the request fails - */ - std::string Post(const std::string& url, const std::string& data) override; - - private: - /** - * @brief Perform checks that the request can be retried - */ - bool CanRetryRequest(bool lastAttempt, long httpCode); - - /** - * @brief Process retry and perform wait logic before retrying the request - */ - void ProcessRetry(int attempt, const Result& httpResult); - - protected: - /** - * @brief Perform a REST request to the given @param url with the given @param headers - * @return The response body - * @throws SFSException if the request fails - */ - virtual std::string CurlPerform(const std::string& url, CurlHeaderList& headers); - - CURL* m_handle; -}; -} // namespace details -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.cpp b/src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.cpp deleted file mode 100644 index 9623387aa7..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "CurlConnectionManager.h" - -#include "../ErrorHandling.h" -#include "CurlConnection.h" - -#include - -using namespace SFS; -using namespace SFS::details; - -namespace -{ -// Curl recommends checking for expected features in runtime -void CheckCurlFeatures(const ReportingHandler& handler) -{ - curl_version_info_data* ver = curl_version_info(CURLVERSION_NOW); - THROW_CODE_IF_NOT_LOG(HttpUnexpected, ver, handler); - - THROW_CODE_IF_NOT_LOG(HttpUnexpected, (ver->features & CURL_VERSION_SSL), handler, "Curl was not built with SSL"); - THROW_CODE_IF_NOT_LOG(HttpUnexpected, - (ver->features & CURL_VERSION_THREADSAFE), - handler, - "Curl is not thread safe"); - - // For thread safety we need the DNS resolutions to be asynchronous (which happens because of c-ares) - THROW_CODE_IF_NOT_LOG(HttpUnexpected, - (ver->features & CURL_VERSION_ASYNCHDNS), - handler, - "Curl was not built with async DNS resolutions"); -} -} // namespace - -CurlConnectionManager::CurlConnectionManager(const ReportingHandler& handler) : ConnectionManager(handler) -{ - THROW_CODE_IF_NOT_LOG(HttpUnexpected, - curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK, - m_handler, - "Curl failed to initialize"); - CheckCurlFeatures(m_handler); -} - -CurlConnectionManager::~CurlConnectionManager() -{ - curl_global_cleanup(); -} - -std::unique_ptr CurlConnectionManager::MakeConnection(const ConnectionConfig& config) -{ - return std::make_unique(config, m_handler); -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.h b/src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.h deleted file mode 100644 index 98e0a5216b..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnectionManager.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "ConnectionManager.h" - -#include - -namespace SFS::details -{ -class Connection; -class ReportingHandler; -struct ConnectionConfig; - -class CurlConnectionManager : public ConnectionManager -{ - public: - CurlConnectionManager(const ReportingHandler& handler); - ~CurlConnectionManager() override; - - std::unique_ptr MakeConnection(const ConnectionConfig& config) override; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.cpp b/src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.cpp deleted file mode 100644 index e09cd22573..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "HttpHeader.h" - -#include "../OSInfo.h" - -#include - -using namespace std::string_literals; - -constexpr const char* c_userAgent = "Microsoft-SFSClient/" SFS_VERSION; - -std::string SFS::details::ToString(HttpHeader header) -{ - switch (header) - { - case HttpHeader::ContentType: - return "Content-Type"; - case HttpHeader::MSCV: - return microsoft::correlation_vector::HEADER_NAME; - case HttpHeader::RetryAfter: - return "Retry-After"; - case HttpHeader::UserAgent: - return "User-Agent"; - } - - return ""; -} - -std::string SFS::details::GetUserAgentValue() -{ - // Examples: - // - Microsoft-SFSClient/1.0.0 (Windows; x64) - // - Microsoft-SFSClient/1.0.0 (Linux; x86_64) - - return c_userAgent + " ("s + osinfo::GetPlatform() + "; "s + osinfo::GetOSMachineInfo() + ")"s; -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.h b/src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.h deleted file mode 100644 index 9a7df689b6..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/HttpHeader.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include - -namespace SFS::details -{ -enum class HttpHeader -{ - ContentType, - MSCV, - RetryAfter, - UserAgent, -}; - -std::string ToString(HttpHeader header); - -std::string GetUserAgentValue(); -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.cpp b/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.cpp deleted file mode 100644 index 217a44850b..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "MockConnection.h" - -using namespace SFS; -using namespace SFS::details; - -MockConnection::MockConnection(const ConnectionConfig& config, const ReportingHandler& handler) - : Connection(config, handler) -{ -} - -MockConnection::~MockConnection() -{ -} - -std::string MockConnection::Get(const std::string&) -{ - return {}; -} - -std::string MockConnection::Post(const std::string&, const std::string&) -{ - return {}; -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.h b/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.h deleted file mode 100644 index 81eb86b929..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnection.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "../Connection.h" - -#include - -namespace SFS::details -{ -class ReportingHandler; - -class MockConnection : public Connection -{ - public: - MockConnection(const ConnectionConfig& config, const ReportingHandler& handler); - ~MockConnection() override; - - std::string Get(const std::string& url) override; - std::string Post(const std::string& url, const std::string& data) override; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.cpp b/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.cpp deleted file mode 100644 index 1d879ecf03..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "MockConnectionManager.h" - -#include "MockConnection.h" - -using namespace SFS::details; - -MockConnectionManager::MockConnectionManager(const ReportingHandler& handler) : ConnectionManager(handler) -{ -} - -MockConnectionManager::~MockConnectionManager() -{ -} - -std::unique_ptr MockConnectionManager::MakeConnection(const ConnectionConfig& config) -{ - return std::make_unique(config, m_handler); -} diff --git a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.h b/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.h deleted file mode 100644 index 9a8965bdd1..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/connection/mock/MockConnectionManager.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "../ConnectionManager.h" - -#include - -namespace SFS::details -{ -class Connection; -class ReportingHandler; -struct ConnectionConfig; - -class MockConnectionManager : public ConnectionManager -{ - public: - MockConnectionManager(const ReportingHandler& handler); - ~MockConnectionManager() override; - - std::unique_ptr MakeConnection(const ConnectionConfig& config) override; -}; -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp b/src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp deleted file mode 100644 index ce5680f166..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentType.h" - -using namespace SFS::details; - -std::string SFS::details::ToString(ContentType type) -{ - switch (type) - { - case ContentType::Generic: - return "Generic"; - case ContentType::App: - return "App"; - default: - return "Unknown"; - } -} diff --git a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.h b/src/SfsClient/sfs-client/client/src/details/entity/ContentType.h deleted file mode 100644 index 2b9ccc4aa5..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include - -namespace SFS::details -{ -enum class ContentType -{ - Generic, - App, -}; - -std::string ToString(ContentType type); -} // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp b/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp deleted file mode 100644 index 53e8f114e7..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "FileEntity.h" - -#include "../ErrorHandling.h" -#include "../ReportingHandler.h" -#include "../Util.h" -#include "AppFile.h" -#include "File.h" - -#include - -#define THROW_INVALID_RESPONSE_IF_NOT(condition, message, handler) \ - THROW_CODE_IF_NOT_LOG(ServiceInvalidResponse, condition, handler, message) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::util; -using json = nlohmann::json; - -namespace -{ -HashType HashTypeFromString(const std::string& hashType, const ReportingHandler& handler) -{ - if (AreEqualI(hashType, "Sha1")) - { - return HashType::Sha1; - } - else if (AreEqualI(hashType, "Sha256")) - { - return HashType::Sha256; - } - else - { - THROW_LOG(Result(Result::Unexpected, "Unknown hash type: " + hashType), handler); - return HashType::Sha1; // Unreachable code, but the compiler doesn't know that. - } -} - -Architecture ArchitectureFromString(const std::string& arch, const ReportingHandler& handler) -{ - if (AreEqualI(arch, "None")) - { - return Architecture::None; - } - else if (AreEqualI(arch, "x86")) - { - return Architecture::x86; - } - else if (AreEqualI(arch, "amd64")) - { - return Architecture::Amd64; - } - else if (AreEqualI(arch, "arm")) - { - return Architecture::Arm; - } - else if (AreEqualI(arch, "arm64")) - { - return Architecture::Arm64; - } - else - { - THROW_LOG(Result(Result::Unexpected, "Unknown architecture: " + arch), handler); - return Architecture::None; // Unreachable code, but the compiler doesn't know that. - } -} - -void ValidateContentType(const FileEntity& entity, ContentType expectedType, const ReportingHandler& handler) -{ - THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType, - entity.GetContentType() != expectedType, - handler, - "The service returned file \"" + entity.fileId + "\" with content type [" + - ToString(entity.GetContentType()) + "] while the expected type was [" + - ToString(expectedType) + "]"); -} -} // namespace - -std::unique_ptr FileEntity::FromJson(const nlohmann::json& file, const ReportingHandler& handler) -{ - // Expected format for a generic file entity: - // { - // "FileId": , - // "Url": , - // "SizeInBytes": , - // "Hashes": { - // "Sha1": , - // "Sha256": - // }, - // "DeliveryOptimization": {} // ignored, not used by the client. - // } - // - // Expected extra elements for an app version entity: - // { - // ... - // "ApplicabilityDetails": { - // "Architectures": [ - // "" - // ], - // "PlatformApplicabilityForPackage": [ - // "" - // ] - // }, - // "FileMoniker": "", - // } - - THROW_INVALID_RESPONSE_IF_NOT(file.is_object(), "File is not a JSON object", handler); - - std::unique_ptr tmp; - const bool isAppEntity = file.contains("FileMoniker"); - if (isAppEntity) - { - tmp = std::make_unique(); - } - else - { - tmp = std::make_unique(); - } - - THROW_INVALID_RESPONSE_IF_NOT(file.contains("FileId"), "Missing File.FileId in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(file["FileId"].is_string(), "File.FileId is not a string", handler); - tmp->fileId = file["FileId"]; - - THROW_INVALID_RESPONSE_IF_NOT(file.contains("Url"), "Missing File.Url in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(file["Url"].is_string(), "File.Url is not a string", handler); - tmp->url = file["Url"]; - - THROW_INVALID_RESPONSE_IF_NOT(file.contains("SizeInBytes"), "Missing File.SizeInBytes in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(file["SizeInBytes"].is_number_unsigned(), - "File.SizeInBytes is not an unsigned number", - handler); - tmp->sizeInBytes = file["SizeInBytes"]; - - THROW_INVALID_RESPONSE_IF_NOT(file.contains("Hashes"), "Missing File.Hashes in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(file["Hashes"].is_object(), "File.Hashes is not an object", handler); - - for (const auto& [hashType, hashValue] : file["Hashes"].items()) - { - THROW_INVALID_RESPONSE_IF_NOT(hashValue.is_string(), "File.Hashes object value is not a string", handler); - tmp->hashes[hashType] = hashValue; - } - - if (isAppEntity) - { - auto appEntity = dynamic_cast(tmp.get()); - - THROW_INVALID_RESPONSE_IF_NOT(file["FileMoniker"].is_string(), "File.FileMoniker is not a string", handler); - appEntity->fileMoniker = file["FileMoniker"]; - - THROW_INVALID_RESPONSE_IF_NOT(file.contains("ApplicabilityDetails"), - "Missing File.ApplicabilityDetails in response", - handler); - - const auto& details = file["ApplicabilityDetails"]; - THROW_INVALID_RESPONSE_IF_NOT(details.is_object(), "File.ApplicabilityDetails is not an object", handler); - - THROW_INVALID_RESPONSE_IF_NOT(details.contains("Architectures"), - "Missing File.ApplicabilityDetails.Architectures in response", - handler); - THROW_INVALID_RESPONSE_IF_NOT(details["Architectures"].is_array(), - "File.ApplicabilityDetails.Architectures is not an array", - handler); - for (const auto& arch : details["Architectures"]) - { - THROW_INVALID_RESPONSE_IF_NOT(arch.is_string(), - "File.ApplicabilityDetails.Architectures array value is not a string", - handler); - } - appEntity->applicabilityDetails.architectures = details["Architectures"]; - - THROW_INVALID_RESPONSE_IF_NOT(details.contains("PlatformApplicabilityForPackage"), - "Missing File.ApplicabilityDetails.PlatformApplicabilityForPackage in response", - handler); - THROW_INVALID_RESPONSE_IF_NOT(details["PlatformApplicabilityForPackage"].is_array(), - "File.ApplicabilityDetails.PlatformApplicabilityForPackage is not an array", - handler); - for (const auto& app : details["PlatformApplicabilityForPackage"]) - { - THROW_INVALID_RESPONSE_IF_NOT( - app.is_string(), - "File.ApplicabilityDetails.PlatformApplicabilityForPackage array value is not a string", - handler); - } - appEntity->applicabilityDetails.platformApplicabilityForPackage = details["PlatformApplicabilityForPackage"]; - } - - return tmp; -} - -FileEntities FileEntity::DownloadInfoResponseToFileEntities(const nlohmann::json& data, const ReportingHandler& handler) -{ - // Expected format is an array of FileEntity - THROW_CODE_IF_NOT_LOG(ServiceInvalidResponse, data.is_array(), handler, "Response is not a JSON array"); - - FileEntities tmp; - for (const auto& fileData : data) - { - THROW_CODE_IF_NOT_LOG(ServiceInvalidResponse, - fileData.is_object(), - handler, - "Array element is not a JSON object"); - tmp.push_back(std::move(FileEntity::FromJson(fileData, handler))); - } - - return tmp; -} - -ContentType GenericFileEntity::GetContentType() const -{ - return ContentType::Generic; -} - -std::unique_ptr GenericFileEntity::ToFile(FileEntity&& entity, const ReportingHandler& handler) -{ - ValidateContentType(entity, ContentType::Generic, handler); - - std::unordered_map hashes; - for (auto& [hashType, hashValue] : entity.hashes) - { - hashes[HashTypeFromString(hashType, handler)] = std::move(hashValue); - } - - std::unique_ptr tmp; - THROW_IF_FAILED_LOG( - File::Make(std::move(entity.fileId), std::move(entity.url), entity.sizeInBytes, std::move(hashes), tmp), - handler); - return tmp; -} - -std::vector GenericFileEntity::FileEntitiesToFileVector(FileEntities&& entities, const ReportingHandler& handler) -{ - std::vector tmp; - for (auto& entity : entities) - { - tmp.push_back(std::move(*GenericFileEntity::ToFile(std::move(*entity), handler))); - } - - return tmp; -} - -ContentType AppFileEntity::GetContentType() const -{ - return ContentType::App; -} - -std::unique_ptr AppFileEntity::ToAppFile(FileEntity&& entity, const ReportingHandler& handler) -{ - ValidateContentType(entity, ContentType::App, handler); - - auto appEntity = dynamic_cast(entity); - - std::unordered_map hashes; - for (auto& [hashType, hashValue] : appEntity.hashes) - { - hashes[HashTypeFromString(hashType, handler)] = std::move(hashValue); - } - - std::vector architectures; - for (auto& arch : appEntity.applicabilityDetails.architectures) - { - architectures.push_back(ArchitectureFromString(arch, handler)); - } - - std::unique_ptr tmp; - THROW_IF_FAILED_LOG(AppFile::Make(std::move(appEntity.fileId), - std::move(appEntity.url), - appEntity.sizeInBytes, - std::move(hashes), - std::move(architectures), - std::move(appEntity.applicabilityDetails.platformApplicabilityForPackage), - std::move(appEntity.fileMoniker), - tmp), - handler); - return tmp; -} - -std::vector AppFileEntity::FileEntitiesToAppFileVector(std::vector>&& entities, - const ReportingHandler& handler) -{ - std::vector tmp; - for (auto& entity : entities) - { - tmp.push_back(std::move(*AppFileEntity::ToAppFile(std::move(*entity), handler))); - } - - return tmp; -} diff --git a/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.h b/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.h deleted file mode 100644 index 2bc5f4a788..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "ContentType.h" - -#include -#include -#include -#include - -#include - -namespace SFS -{ -class File; -class AppFile; - -namespace details -{ -class ReportingHandler; - -struct FileEntity; - -using FileEntities = std::vector>; - -struct FileEntity -{ - virtual ~FileEntity() - { - } - - virtual ContentType GetContentType() const = 0; - - std::string fileId; - std::string url; - uint64_t sizeInBytes; - std::unordered_map hashes; - - static std::unique_ptr FromJson(const nlohmann::json& file, const ReportingHandler& handler); - static FileEntities DownloadInfoResponseToFileEntities(const nlohmann::json& data, const ReportingHandler& handler); -}; - -struct GenericFileEntity : public FileEntity -{ - ContentType GetContentType() const override; - - static std::unique_ptr ToFile(FileEntity&& entity, const ReportingHandler& handler); - static std::vector FileEntitiesToFileVector(FileEntities&& entities, const ReportingHandler& handler); -}; - -struct ApplicabilityDetailsEntity -{ - std::vector architectures; - std::vector platformApplicabilityForPackage; -}; - -struct AppFileEntity : public FileEntity -{ - ContentType GetContentType() const override; - - std::string fileMoniker; - ApplicabilityDetailsEntity applicabilityDetails; - - static std::unique_ptr ToAppFile(FileEntity&& entity, const ReportingHandler& handler); - static std::vector FileEntitiesToAppFileVector(FileEntities&& entities, const ReportingHandler& handler); -}; - -} // namespace details -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp b/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp deleted file mode 100644 index eacd847e88..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "VersionEntity.h" - -#include "../ErrorHandling.h" -#include "../ReportingHandler.h" -#include "ContentId.h" - -#include - -#define THROW_INVALID_RESPONSE_IF_NOT(condition, message, handler) \ - THROW_CODE_IF_NOT_LOG(ServiceInvalidResponse, condition, handler, message) - -using namespace SFS; -using namespace SFS::details; -using json = nlohmann::json; - -namespace -{ -void ValidateContentType(const VersionEntity& entity, ContentType expectedType, const ReportingHandler& handler) -{ - THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType, - entity.GetContentType() != expectedType, - handler, - "The service returned entity \"" + entity.contentId.name + "\" with content type [" + - ToString(entity.GetContentType()) + "] while the expected type was [" + - ToString(expectedType) + "]"); -} -} // namespace - -std::unique_ptr VersionEntity::FromJson(const nlohmann::json& data, const ReportingHandler& handler) -{ - // Expected format for a generic version entity: - // { - // "ContentId": { - // "Namespace": , - // "Name": , - // "Version": - // } - // } - // - // Expected extra elements for an app version entity: - // { - // ... - // "UpdateId": "", - // "Prerequisites": [ - // { - // "Namespace": "", - // "Name": "", - // "Version": "" - // } - // ] - // } - - THROW_INVALID_RESPONSE_IF_NOT(data.is_object(), "Response is not a JSON object", handler); - - std::unique_ptr tmp; - const bool isAppEntity = data.contains("UpdateId"); - if (isAppEntity) - { - tmp = std::make_unique(); - } - else - { - tmp = std::make_unique(); - } - - THROW_INVALID_RESPONSE_IF_NOT(data.contains("ContentId"), "Missing ContentId in response", handler); - - const auto& contentId = data["ContentId"]; - THROW_INVALID_RESPONSE_IF_NOT(contentId.is_object(), "ContentId is not a JSON object", handler); - - THROW_INVALID_RESPONSE_IF_NOT(contentId.contains("Namespace"), "Missing ContentId.Namespace in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(contentId["Namespace"].is_string(), "ContentId.Namespace is not a string", handler); - tmp->contentId.nameSpace = contentId["Namespace"]; - - THROW_INVALID_RESPONSE_IF_NOT(contentId.contains("Name"), "Missing ContentId.Name in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(contentId["Name"].is_string(), "ContentId.Name is not a string", handler); - tmp->contentId.name = contentId["Name"]; - - THROW_INVALID_RESPONSE_IF_NOT(contentId.contains("Version"), "Missing ContentId.Version in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(contentId["Version"].is_string(), "ContentId.Version is not a string", handler); - tmp->contentId.version = contentId["Version"]; - - if (isAppEntity) - { - auto appEntity = dynamic_cast(tmp.get()); - - THROW_INVALID_RESPONSE_IF_NOT(data["UpdateId"].is_string(), "UpdateId is not a string", handler); - appEntity->updateId = data["UpdateId"]; - - THROW_INVALID_RESPONSE_IF_NOT(data.contains("Prerequisites"), "Missing Prerequisites in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(data["Prerequisites"].is_array(), "Prerequisites is not an array", handler); - - for (const auto& prereq : data["Prerequisites"]) - { - THROW_INVALID_RESPONSE_IF_NOT(prereq.is_object(), "Prerequisite element is not a JSON object", handler); - - GenericVersionEntity prereqEntity; - THROW_INVALID_RESPONSE_IF_NOT(prereq.contains("Namespace"), - "Missing Prerequisite.Namespace in response", - handler); - THROW_INVALID_RESPONSE_IF_NOT(prereq["Namespace"].is_string(), - "Prerequisite.Namespace is not a string", - handler); - prereqEntity.contentId.nameSpace = prereq["Namespace"]; - - THROW_INVALID_RESPONSE_IF_NOT(prereq.contains("Name"), "Missing Prerequisite.Name in response", handler); - THROW_INVALID_RESPONSE_IF_NOT(prereq["Name"].is_string(), "Prerequisite.Name is not a string", handler); - prereqEntity.contentId.name = prereq["Name"]; - - THROW_INVALID_RESPONSE_IF_NOT(prereq.contains("Version"), - "Missing Prerequisite.Version in response", - handler); - THROW_INVALID_RESPONSE_IF_NOT(prereq["Version"].is_string(), - "Prerequisite.Version is not a string", - handler); - prereqEntity.contentId.version = prereq["Version"]; - - appEntity->prerequisites.push_back(std::move(prereqEntity)); - } - } - return tmp; -} - -std::unique_ptr VersionEntity::ToContentId(VersionEntity&& entity, const ReportingHandler& handler) -{ - std::unique_ptr tmp; - THROW_IF_FAILED_LOG(ContentId::Make(std::move(entity.contentId.nameSpace), - std::move(entity.contentId.name), - std::move(entity.contentId.version), - tmp), - handler); - return tmp; -} - -ContentType GenericVersionEntity::GetContentType() const -{ - return ContentType::Generic; -} - -ContentType AppVersionEntity::GetContentType() const -{ - return ContentType::App; -} - -AppVersionEntity* AppVersionEntity::GetAppVersionEntityPtr(std::unique_ptr& versionEntity, - const ReportingHandler& handler) -{ - ValidateContentType(*versionEntity, ContentType::App, handler); - return dynamic_cast(versionEntity.get()); -} diff --git a/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.h b/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.h deleted file mode 100644 index 3b6cb42705..0000000000 --- a/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "ContentType.h" - -#include -#include -#include - -#include - -namespace SFS -{ -class ContentId; - -namespace details -{ -class ReportingHandler; - -struct ContentIdEntity -{ - std::string nameSpace; - std::string name; - std::string version; -}; - -struct VersionEntity -{ - virtual ~VersionEntity() - { - } - - virtual ContentType GetContentType() const = 0; - - ContentIdEntity contentId; - - static std::unique_ptr FromJson(const nlohmann::json& data, const ReportingHandler& handler); - static std::unique_ptr ToContentId(VersionEntity&& entity, const ReportingHandler& handler); -}; - -struct GenericVersionEntity : public VersionEntity -{ - ContentType GetContentType() const override; -}; - -struct AppVersionEntity : public VersionEntity -{ - ContentType GetContentType() const override; - - std::string updateId; - std::vector prerequisites; - - static AppVersionEntity* GetAppVersionEntityPtr(std::unique_ptr& versionEntity, - const ReportingHandler& handler); -}; - -using VersionEntities = std::vector>; -} // namespace details -} // namespace SFS diff --git a/src/SfsClient/sfs-client/client/tests/CMakeLists.txt b/src/SfsClient/sfs-client/client/tests/CMakeLists.txt deleted file mode 100644 index 5782701e10..0000000000 --- a/src/SfsClient/sfs-client/client/tests/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -find_package(Catch2 CONFIG REQUIRED) -include(Catch) - -# For mock http server -find_package(httplib CONFIG REQUIRED) - -project(SFSClientTests LANGUAGES CXX) - -add_executable(${PROJECT_NAME}) - -target_sources( - ${PROJECT_NAME} - PRIVATE functional/details/CurlConnectionTests.cpp - functional/details/SFSClientImplTests.cpp - functional/SFSClientTests.cpp - mock/MockWebServer.cpp - mock/ProxyServer.cpp - mock/ServerCommon.cpp - unit/AppContentTests.cpp - unit/AppFileTests.cpp - unit/ApplicabilityDetailsTests.cpp - unit/ContentIdTests.cpp - unit/ContentTests.cpp - unit/details/CurlConnectionManagerTests.cpp - unit/details/CurlConnectionTests.cpp - unit/details/entity/FileEntityTests.cpp - unit/details/entity/VersionEntityTests.cpp - unit/details/EnvTests.cpp - unit/details/ErrorHandlingTests.cpp - unit/details/ReportingHandlerTests.cpp - unit/details/SFSClientImplTests.cpp - unit/details/SFSUrlBuilderTests.cpp - unit/details/TestOverrideTests.cpp - unit/details/UrlBuilderTests.cpp - unit/details/UtilTests.cpp - unit/FileTests.cpp - unit/ResultTests.cpp - unit/SFSClientTests.cpp - util/SFSExceptionMatcher.cpp - util/TestHelper.cpp) - -set(SFS_CLIENT_LIB_NAME "Microsoft::${CMAKE_PROJECT_NAME}") -add_dependencies(${PROJECT_NAME} ${SFS_CLIENT_LIB_NAME}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${SFS_CLIENT_LIB_NAME}) -target_link_libraries(${PROJECT_NAME} PRIVATE Catch2::Catch2WithMain) -target_link_libraries(${PROJECT_NAME} PRIVATE httplib::httplib) - -target_include_directories(${PROJECT_NAME} PUBLIC ../include) -target_include_directories(${PROJECT_NAME} PUBLIC ../src/details) - -catch_discover_tests(${PROJECT_NAME}) - -set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY - ${CMAKE_BINARY_DIR}/tests/bin/) - -set_compile_options_for_target(${PROJECT_NAME}) - -if(SFS_ENABLE_TEST_OVERRIDES) - target_compile_definitions(${PROJECT_NAME} - PRIVATE SFS_ENABLE_TEST_OVERRIDES=1) -endif() diff --git a/src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp b/src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp deleted file mode 100644 index f5b90bc942..0000000000 --- a/src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../mock/MockWebServer.h" -#include "../mock/ProxyServer.h" -#include "../util/TestHelper.h" -#include "TestOverride.h" -#include "sfsclient/SFSClient.h" - -#include - -#include - -#define TEST(...) TEST_CASE("[Functional][SFSClientTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::test; -using namespace std::chrono; - -const std::string c_instanceId = "testInstanceId"; -const std::string c_namespace = "testNamespace"; -const std::string c_productName = "testProduct"; -const std::string c_version = "0.0.1"; -const std::string c_nextVersion = "0.0.2"; - -namespace -{ -void CheckContentId(const ContentId& contentId, const std::string& name, const std::string& version) -{ - REQUIRE(contentId.GetNameSpace() == c_namespace); - REQUIRE(contentId.GetName() == name); - REQUIRE(contentId.GetVersion() == version); -} - -void CheckFiles(const std::vector& files) -{ - REQUIRE(files.size() == 2); - REQUIRE(files[0].GetFileId() == (c_productName + ".json")); - REQUIRE(files[0].GetUrl() == ("http://localhost/1.json")); - REQUIRE(files[1].GetFileId() == (c_productName + ".bin")); - REQUIRE(files[1].GetUrl() == ("http://localhost/2.bin")); -} - -void CheckAppFiles(const std::vector& files, const std::string& name) -{ - REQUIRE(files.size() == 2); - REQUIRE(files[0].GetFileId() == (name + ".json")); - REQUIRE(files[0].GetUrl() == ("http://localhost/1.json")); - REQUIRE_FALSE(files[0].GetFileMoniker().empty()); - REQUIRE_FALSE(files[0].GetApplicabilityDetails().GetArchitectures().empty()); - REQUIRE_FALSE(files[0].GetApplicabilityDetails().GetPlatformApplicabilityForPackage().empty()); - REQUIRE(files[1].GetFileId() == (name + ".bin")); - REQUIRE(files[1].GetUrl() == ("http://localhost/2.bin")); - REQUIRE_FALSE(files[1].GetFileMoniker().empty()); - REQUIRE_FALSE(files[1].GetApplicabilityDetails().GetArchitectures().empty()); - REQUIRE_FALSE(files[1].GetApplicabilityDetails().GetPlatformApplicabilityForPackage().empty()); -} - -void CheckMockContent(const Content& content, const std::string& version) -{ - CheckContentId(content.GetContentId(), c_productName, version); - CheckFiles(content.GetFiles()); -} - -void CheckMockAppContent(const AppContent& content, - const std::string& version, - const std::vector mockPrereqs) -{ - CheckContentId(content.GetContentId(), c_productName, version); - REQUIRE_FALSE(content.GetUpdateId().empty()); - CheckAppFiles(content.GetFiles(), c_productName); - - REQUIRE(content.GetPrerequisites().size() == mockPrereqs.size()); - for (size_t i = 0; i < mockPrereqs.size(); ++i) - { - const auto& prereq = content.GetPrerequisites()[i]; - const auto& mockPrereq = mockPrereqs[i]; - CheckContentId(prereq.GetContentId(), mockPrereq.name, mockPrereq.version); - - CheckAppFiles(prereq.GetFiles(), mockPrereq.name); - } -} -} // namespace - -TEST("Testing SFSClient::GetLatestDownloadInfo()") -{ - if (!AreTestOverridesAllowed()) - { - return; - } - - test::MockWebServer server; - ScopedTestOverride override(TestOverride::BaseUrl, server.GetBaseUrl()); - - std::unique_ptr sfsClient; - REQUIRE(SFSClient::Make({"testAccountId", c_instanceId, c_namespace, LogCallbackToTest}, sfsClient) == - Result::Success); - REQUIRE(sfsClient != nullptr); - - server.RegisterProduct(c_productName, c_version); - - std::vector contents; - - SECTION("Single product request") - { - RequestParams params; - params.baseCV = "aaaaaaaaaaaaaaaa.1"; - SECTION("No attributes") - { - params.productRequests = {{c_productName, {}}}; - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success); - REQUIRE(contents.size() == 1); - CheckMockContent(contents[0], c_version); - } - - SECTION("No attributes + proxy") - { - test::ProxyServer proxy; - - params.productRequests = {{c_productName, {}}}; - params.proxy = proxy.GetBaseUrl(); - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success); - REQUIRE(contents.size() == 1); - CheckMockContent(contents[0], c_version); - - REQUIRE(proxy.Stop() == Result::Success); - } - - SECTION("With attributes") - { - const TargetingAttributes attributes{{"attr1", "value"}}; - params.productRequests = {{c_productName, attributes}}; - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success); - REQUIRE(contents.size() == 1); - CheckMockContent(contents[0], c_version); - } - - SECTION("Wrong product name") - { - params.productRequests = {{"badName", {}}}; - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpNotFound); - REQUIRE(contents.empty()); - - const TargetingAttributes attributes{{"attr1", "value"}}; - params.productRequests = {{"badName", attributes}}; - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpNotFound); - REQUIRE(contents.empty()); - } - - SECTION("Adding new version") - { - server.RegisterProduct(c_productName, c_nextVersion); - - params.productRequests = {{c_productName, {}}}; - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success); - REQUIRE(contents.size() == 1); - CheckMockContent(contents[0], c_nextVersion); - } - } - - REQUIRE(server.Stop() == Result::Success); -} - -TEST("Testing SFSClient::GetLatestAppDownloadInfo()") -{ - if (!AreTestOverridesAllowed()) - { - return; - } - - test::MockWebServer server; - ScopedTestOverride override(TestOverride::BaseUrl, server.GetBaseUrl()); - - const std::string prereq1 = "prereq1"; - const std::string prereq1Version = "1.0"; - const std::string prereq2 = "prereq2"; - const std::string prereq2Version = "2.0"; - - std::unique_ptr sfsClient; - REQUIRE(SFSClient::Make({"testAccountId", "storeapps", c_namespace, LogCallbackToTest}, sfsClient) == - Result::Success); - REQUIRE(sfsClient != nullptr); - - SECTION("App products") - { - auto CheckApp = [&](const std::vector& mockPrereqs) { - server.RegisterAppProduct(c_productName, c_version, mockPrereqs); - - std::vector contents; - - RequestParams params; - params.baseCV = "aaaaaaaaaaaaaaaa.1"; - SECTION("No attributes") - { - params.productRequests = {{c_productName, {}}}; - REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::Success); - REQUIRE(contents.size() == 1); - CheckMockAppContent(contents[0], c_version, mockPrereqs); - } - - SECTION("With attributes") - { - const TargetingAttributes attributes{{"attr1", "value"}}; - params.productRequests = {{c_productName, attributes}}; - REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::Success); - REQUIRE(contents.size() == 1); - CheckMockAppContent(contents[0], c_version, mockPrereqs); - } - - SECTION("Wrong product name") - { - params.productRequests = {{"badName", {}}}; - REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::HttpNotFound); - REQUIRE(contents.empty()); - - const TargetingAttributes attributes{{"attr1", "value"}}; - params.productRequests = {{"badName", attributes}}; - REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::HttpNotFound); - REQUIRE(contents.empty()); - } - - SECTION("Adding new version") - { - server.RegisterAppProduct(c_productName, c_nextVersion, mockPrereqs); - - params.productRequests = {{c_productName, {}}}; - REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::Success); - REQUIRE(contents.size() == 1); - CheckMockAppContent(contents[0], c_nextVersion, mockPrereqs); - } - }; - - SECTION("No prerequisites") - { - CheckApp({}); - } - - SECTION("1 prereq") - { - CheckApp({{prereq1, prereq1Version}}); - } - - SECTION("2 prereqs") - { - CheckApp({{prereq1, prereq1Version}, {prereq2, prereq2Version}}); - } - } - - SECTION("Non-app products") - { - server.RegisterProduct(c_productName, c_version); - - std::vector contents; - - RequestParams params; - params.productRequests = {{c_productName, {}}}; - auto result = sfsClient->GetLatestAppDownloadInfo(params, contents); - REQUIRE(result.GetCode() == Result::ServiceUnexpectedContentType); - REQUIRE( - result.GetMsg() == - R"(The service returned entity "testProduct" with content type [Generic] while the expected type was [App])"); - REQUIRE(contents.empty()); - } - - REQUIRE(server.Stop() == Result::Success); -} - -TEST("Testing SFSClient retry behavior") -{ - if (!AreTestOverridesAllowed()) - { - INFO("Skipping. Test overrides not enabled"); - return; - } - - MockWebServer server; - ScopedTestOverride urlOverride(TestOverride::BaseUrl, server.GetBaseUrl()); - - server.RegisterProduct(c_productName, c_version); - RequestParams params; - params.productRequests = {{c_productName, {}}}; - std::vector contents; - - std::unique_ptr sfsClient; - ClientConfig clientConfig{"testAccountId", c_instanceId, c_namespace, LogCallbackToTest}; - - SECTION("Test exponential backoff") - { - INFO("Sets the retry delay to 50ms to speed up the test"); - ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 50); - - REQUIRE(SFSClient::Make(clientConfig, sfsClient)); - REQUIRE(sfsClient != nullptr); - - const int retriableError = 503; // ServerBusy - - auto RunTimedGet = [&](bool success = true) -> long long { - auto begin = steady_clock::now(); - if (success) - { - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents)); - REQUIRE(contents.size() == 1); - } - else - { - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable); - REQUIRE(contents.empty()); - } - auto end = steady_clock::now(); - return duration_cast(end - begin).count(); - }; - - long long allowedTimeDeviation = 200LL; - std::queue forcedHttpErrors({retriableError}); - SECTION("Should take at least 50ms with a single retriable error") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 50LL); - REQUIRE(time < 50LL + allowedTimeDeviation); - } - - forcedHttpErrors.push(retriableError); - SECTION("Should take at least 150ms (50ms + 2*50ms) with two retriable errors") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 150LL); - REQUIRE(time < 150LL + allowedTimeDeviation); - } - - forcedHttpErrors.push(retriableError); - SECTION("Should take at least 300ms (50ms + 2*50ms + 3*50ms) with three retriable errors") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 300LL); - REQUIRE(time < 300LL + allowedTimeDeviation); - } - - forcedHttpErrors.push(retriableError); - SECTION("Should take at least 300ms (50ms + 2*50ms + 3*50ms) with four retriable errors, but fail") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(false /*success*/); - REQUIRE(time >= 300LL); - REQUIRE(time < 300LL + allowedTimeDeviation); - } - } - - SECTION("Test retriable errors with Retry-After headers") - { - INFO("Sets the retry delay to 200ms to speed up the test"); - ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 200); - - REQUIRE(SFSClient::Make(clientConfig, sfsClient)); - REQUIRE(sfsClient != nullptr); - - const int retriableError = 503; // ServerBusy - const int retriableError2 = 502; // BadGateway - - auto RunTimedGet = [&]() -> long long { - auto begin = steady_clock::now(); - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents)); - REQUIRE(contents.size() == 1); - auto end = steady_clock::now(); - return duration_cast(end - begin).count(); - }; - - std::unordered_map headersByCode; - headersByCode[retriableError] = {{"Retry-After", "1"}}; // 1s delay - server.SetResponseHeaders(headersByCode); - - long long allowedTimeDeviation = 200LL; - std::queue forcedHttpErrors({retriableError}); - SECTION("Should take at least 1000ms with a single retriable error with 1s in Retry-After") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 1000LL); - REQUIRE(time < 1000LL + allowedTimeDeviation); - } - - SECTION( - "Should take at least 1000ms + 200ms with a retriable error with 1s in Retry-After and one with 200ms*2 as default value") - { - forcedHttpErrors.push(retriableError2); - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 1400LL); - REQUIRE(time < 1400LL + allowedTimeDeviation); - } - } - - SECTION("Test maxRetries") - { - INFO("Sets the retry delay to 1ms to speed up the test"); - ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 1); - - const int retriableError = 503; // ServerBusy - - SECTION("With default retries") - { - REQUIRE(SFSClient::Make(clientConfig, sfsClient)); - REQUIRE(sfsClient != nullptr); - - SECTION("Should pass with 3 errors") - { - server.SetForcedHttpErrors(std::queue({retriableError, retriableError, retriableError})); - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents)); - } - - SECTION("Should fail with 4 errors") - { - server.SetForcedHttpErrors( - std::queue({retriableError, retriableError, retriableError, retriableError})); - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable); - } - } - - SECTION("Forcing retries to 3") - { - params.retryOnError = true; - REQUIRE(SFSClient::Make(clientConfig, sfsClient)); - REQUIRE(sfsClient != nullptr); - - SECTION("Should pass with 3 errors") - { - server.SetForcedHttpErrors(std::queue({retriableError, retriableError, retriableError})); - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents)); - } - - SECTION("Should fail with 4 errors") - { - server.SetForcedHttpErrors( - std::queue({retriableError, retriableError, retriableError, retriableError})); - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable); - } - } - - SECTION("Reducing retries to 0") - { - params.retryOnError = false; - REQUIRE(SFSClient::Make(clientConfig, sfsClient)); - REQUIRE(sfsClient != nullptr); - - SECTION("Should pass with no errors") - { - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents)); - } - - SECTION("Should fail with 1 error") - { - server.SetForcedHttpErrors(std::queue({retriableError})); - REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable); - } - } - } - - REQUIRE(server.Stop() == Result::Success); -} diff --git a/src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp b/src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp deleted file mode 100644 index 77c114395a..0000000000 --- a/src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp +++ /dev/null @@ -1,611 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../mock/MockWebServer.h" -#include "../../mock/ProxyServer.h" -#include "../../util/SFSExceptionMatcher.h" -#include "../../util/TestHelper.h" -#include "ReportingHandler.h" -#include "SFSUrlBuilder.h" -#include "TestOverride.h" -#include "connection/CurlConnection.h" -#include "connection/CurlConnectionManager.h" -#include "connection/HttpHeader.h" - -#include -#include -#include -#include - -#include -#include - -#define TEST(...) TEST_CASE("[Functional][CurlConnectionTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; -using namespace std::chrono; -using json = nlohmann::json; - -const std::string c_instanceId = "default"; -const std::string c_namespace = "default"; -const std::string c_productName = "testProduct"; -const std::string c_version = "0.0.1"; -const std::string c_nextVersion = "0.0.2"; - -namespace -{ -class CurlConnectionTimeout : public CurlConnection -{ - public: - CurlConnectionTimeout(const ConnectionConfig& config, const ReportingHandler& handler) - : CurlConnection(config, handler) - { - } - - std::string Get(const std::string& url) override - { - // Timeout within 1ms - curl_easy_setopt(m_handle, CURLOPT_TIMEOUT_MS, 1L); - return CurlConnection::Get(url); - } - - std::string Post(const std::string& url, const std::string& data) override - { - // Timeout within 1ms - curl_easy_setopt(m_handle, CURLOPT_TIMEOUT_MS, 1L); - return CurlConnection::Post(url, data); - } -}; - -class CurlConnectionTimeoutManager : public CurlConnectionManager -{ - public: - CurlConnectionTimeoutManager(const ReportingHandler& handler) : CurlConnectionManager(handler) - { - } - - std::unique_ptr MakeConnection(const ConnectionConfig& config) override - { - return std::make_unique(config, m_handler); - } -}; - -std::string TimestampToHttpDateString(std::chrono::time_point time) -{ - auto timer = system_clock::to_time_t(time); - - std::stringstream timeStream; - struct tm gmTime; -#ifdef _WIN32 - gmtime_s(&gmTime, &timer); // gmtime_s is the safe version of gmtime, not available on Linux -#else - gmTime = (*std::gmtime(&timer)); -#endif - timeStream << std::put_time(&gmTime, "%a, %d %b %Y %X GMT"); // day, dd Mmm yyyy HH:MM:SS - - return timeStream.str(); -} -} // namespace - -TEST("Testing CurlConnection()") -{ - test::MockWebServer server; - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - CurlConnectionManager connectionManager(handler); - auto connection = connectionManager.MakeConnection({}); - SFSUrlBuilder urlBuilder(SFSCustomUrl(server.GetBaseUrl()), c_instanceId, c_namespace, handler); - server.RegisterExpectedRequestHeader(HttpHeader::UserAgent, GetUserAgentValue()); - - SECTION("Testing CurlConnection::Get()") - { - const std::string url = urlBuilder.GetSpecificVersionUrl(c_productName, c_version); - - json expectedResponse; - expectedResponse["ContentId"] = {{"Namespace", "default"}, {"Name", c_productName}, {"Version", c_version}}; - - SECTION("Direct connection") - { - // Before registering the product, the URL returns 404 Not Found - REQUIRE_THROWS_CODE(connection->Get(url), HttpNotFound); - - // Register the product - server.RegisterProduct(c_productName, c_version); - - // After registering the product, the URL returns 200 OK - std::string out; - REQUIRE_NOTHROW(out = connection->Get(url)); - - REQUIRE(json::parse(out) == expectedResponse); - } - - SECTION("With proxy") - { - test::ProxyServer proxy; - - ConnectionConfig config; - config.proxy = proxy.GetBaseUrl(); - connection = connectionManager.MakeConnection(config); - - // Before registering the product, the URL returns 404 Not Found - REQUIRE_THROWS_CODE(connection->Get(url), HttpNotFound); - - // Register the product - server.RegisterProduct(c_productName, c_version); - - // After registering the product, the URL returns 200 OK - std::string out; - REQUIRE_NOTHROW(out = connection->Get(url)); - - REQUIRE(json::parse(out) == expectedResponse); - - REQUIRE(proxy.Stop() == Result::Success); - } - } - - SECTION("Testing CurlConnection::Post()") - { - server.RegisterExpectedRequestHeader(HttpHeader::ContentType, "application/json"); - - SECTION("With GetLatestVersionBatch mock") - { - const std::string url = urlBuilder.GetLatestVersionBatchUrl(); - - // Missing proper body returns HttpBadRequest - REQUIRE_THROWS_CODE(connection->Post(url), HttpBadRequest); - - // Before registering the product, the URL returns 404 Not Found - const json body = {{{"TargetingAttributes", {}}, {"Product", c_productName}}}; - REQUIRE_THROWS_CODE(connection->Post(url, body.dump()), HttpNotFound); - - // Register the product - server.RegisterProduct(c_productName, c_version); - - // After registering the product, the URL returns 200 OK - std::string out; - REQUIRE_NOTHROW(out = connection->Post(url, body.dump())); - - json expectedResponse = json::array(); - expectedResponse.push_back( - {{"ContentId", {{"Namespace", "default"}, {"Name", c_productName}, {"Version", c_version}}}}); - REQUIRE(json::parse(out) == expectedResponse); - - // Returns the next version now - server.RegisterProduct(c_productName, c_nextVersion); - REQUIRE_NOTHROW(out = connection->Post(url, body.dump())); - - expectedResponse = json::array(); - expectedResponse.push_back( - {{"ContentId", {{"Namespace", "default"}, {"Name", c_productName}, {"Version", c_nextVersion}}}}); - REQUIRE(json::parse(out) == expectedResponse); - - SECTION("Testing with proxy") - { - test::ProxyServer proxy; - - ConnectionConfig config; - config.proxy = proxy.GetBaseUrl(); - connection = connectionManager.MakeConnection(config); - - REQUIRE_NOTHROW(out = connection->Post(url, body.dump())); - - REQUIRE(json::parse(out) == expectedResponse); - - REQUIRE(proxy.Stop() == Result::Success); - } - } - - SECTION("With GetDownloadInfo mock") - { - const std::string url = urlBuilder.GetDownloadInfoUrl(c_productName, c_version); - - // Before registering the product, the URL returns 404 Not Found - REQUIRE_THROWS_CODE(connection->Post(url), HttpNotFound); - - // Register the product - server.RegisterProduct(c_productName, c_version); - - // After registering the product, the URL returns 200 OK - std::string out; - REQUIRE_NOTHROW(out = connection->Post(url)); - - json expectedResponse = json::array(); - expectedResponse.push_back({{"Url", "http://localhost/1.json"}, - {"FileId", c_productName + ".json"}, - {"SizeInBytes", 100}, - {"Hashes", {{"Sha1", "123"}, {"Sha256", "456"}}}}); - expectedResponse[0]["DeliveryOptimization"] = {{"CatalogId", "789"}}; - expectedResponse[0]["DeliveryOptimization"]["Properties"] = { - {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/1.json"}, {"HashOfHashes", "abc"}}}}; - - expectedResponse.push_back({{"Url", "http://localhost/2.bin"}, - {"FileId", c_productName + ".bin"}, - {"SizeInBytes", 200}, - {"Hashes", {{"Sha1", "421"}, {"Sha256", "132"}}}}); - expectedResponse[1]["DeliveryOptimization"] = {{"CatalogId", "14"}}; - expectedResponse[1]["DeliveryOptimization"]["Properties"] = { - {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/2.bin"}, {"HashOfHashes", "abcd"}}}}; - - REQUIRE(json::parse(out) == expectedResponse); - - SECTION("Testing with proxy") - { - test::ProxyServer proxy; - - ConnectionConfig config; - config.proxy = proxy.GetBaseUrl(); - connection = connectionManager.MakeConnection(config); - - REQUIRE_NOTHROW(out = connection->Post(url)); - - REQUIRE(json::parse(out) == expectedResponse); - - REQUIRE(proxy.Stop() == Result::Success); - } - } - } - - SECTION("Testing with a malformed url") - { - std::string url = server.GetBaseUrl(); - REQUIRE_THROWS_CODE(connection->Get(url), HttpNotFound); - REQUIRE_THROWS_CODE(connection->Get(url + "/names"), HttpNotFound); - REQUIRE_THROWS_CODE(connection->Post(url + "/names", "{}"), HttpNotFound); - - url = server.GetBaseUrl() + "/api/v1/contents/" + c_instanceId + "/namespaces/" + c_namespace + "/names/" + - c_productName + "/versYions/" + c_version + "/files?action=GenerateDownloadInfo"; - - std::string out; - REQUIRE_THROWS_CODE(connection->Get(url), HttpNotFound); - REQUIRE_THROWS_CODE(connection->Post(url, std::string()), HttpNotFound); - REQUIRE_THROWS_CODE(out = connection->Post(url, {}), HttpNotFound); - CHECK(out.empty()); - } - - REQUIRE(server.Stop() == Result::Success); -} - -TEST("Testing CurlConnection when the server is not reachable") -{ - // Using a custom override class just to time out faster on an invalid URL - ReportingHandler handler; - CurlConnectionTimeoutManager connectionManager(handler); - handler.SetLoggingCallback(LogCallbackToTest); - auto connection = connectionManager.MakeConnection({}); - - // Using a non-routable IP address to ensure the server is not reachable - // https://www.rfc-editor.org/rfc/rfc5737#section-3: The blocks 192.0.2.0/24 (...) are provided for use in - // documentation. - std::string url = "192.0.2.0"; - std::string out; - REQUIRE_THROWS_CODE(connection->Get(url), HttpTimeout); - REQUIRE_THROWS_CODE(connection->Post(url + "/names", "{}"), HttpTimeout); - - url = "192.0.2.0/files?action=GenerateDownloadInfo"; - - REQUIRE_THROWS_CODE_MSG_MATCHES(connection->Get(url), - HttpTimeout, - Catch::Matchers::ContainsSubstring("timed out after")); - REQUIRE_THROWS_CODE_MSG_MATCHES(connection->Post(url, {}), - HttpTimeout, - Catch::Matchers::ContainsSubstring("timed out after")); -} - -TEST("Testing CurlConnection works from a second ConnectionManager") -{ - ReportingHandler handler; - - // Create a first connection manager and see it leave scope - curl initializes and uninitializes - { - CurlConnectionManager connectionManager(handler); - REQUIRE(connectionManager.MakeConnection({}) != nullptr); - } - - test::MockWebServer server; - CurlConnectionManager connectionManager(handler); - handler.SetLoggingCallback(LogCallbackToTest); - auto connection = connectionManager.MakeConnection({}); - SFSUrlBuilder urlBuilder(SFSCustomUrl(server.GetBaseUrl()), c_instanceId, c_namespace, handler); - - const std::string url = urlBuilder.GetSpecificVersionUrl(c_productName, c_version); - - // Register the product - server.RegisterProduct(c_productName, c_version); - - // After registering the product, the URL returns 200 OK - REQUIRE_NOTHROW(connection->Get(url)); - - SECTION("A second connection also works") - { - auto connection2 = connectionManager.MakeConnection({}); - REQUIRE_NOTHROW(connection2->Get(url)); - } -} - -TEST("Testing a url that's too big throws 414") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - test::MockWebServer server; - CurlConnectionManager connectionManager(handler); - auto connection = connectionManager.MakeConnection({}); - SFSUrlBuilder urlBuilder(SFSCustomUrl(server.GetBaseUrl()), c_instanceId, c_namespace, handler); - - // Will use a fake large product name to produce a large url - const std::string largeProductName(90000, 'a'); - - // Register the products - server.RegisterProduct(largeProductName, c_version); - - // Url produces: 414 URI Too Long - std::string out; - REQUIRE_THROWS_CODE_MSG(connection->Get(urlBuilder.GetSpecificVersionUrl(largeProductName, c_version)), - HttpUnexpected, - "Unexpected HTTP code 414"); -} - -TEST("Testing a response over the limit fails the operation") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - test::MockWebServer server; - CurlConnectionManager connectionManager(handler); - auto connection = connectionManager.MakeConnection({}); - SFSUrlBuilder urlBuilder(SFSCustomUrl(server.GetBaseUrl()), c_instanceId, c_namespace, handler); - - // Will use a fake large product name to produce a response over the limit of 100k characters - const std::string largeProductName(90000, 'a'); - const std::string overLimitProductName(1000000, 'a'); - - // Register the products - server.RegisterProduct(largeProductName, c_version); - server.RegisterProduct(overLimitProductName, c_version); - - // Using GetLatestVersionBatch api since the product name is in the body and not in the url, to avoid a 414 error - // like on the test above - const std::string url = urlBuilder.GetLatestVersionBatchUrl(); - - // Large one works - json body = {{{"TargetingAttributes", {}}, {"Product", largeProductName}}}; - REQUIRE_NOTHROW(connection->Post(url, body.dump())); - - // Going over the limit fails with a message like "client returned ERROR on write of 16384 bytes" - body[0]["Product"] = overLimitProductName; - REQUIRE_THROWS_CODE_MSG_MATCHES(connection->Post(url, body.dump()), - ConnectionUnexpectedError, - Catch::Matchers::ContainsSubstring("client returned ERROR on write of")); -} - -TEST("Testing MS-CV is sent to server") -{ - test::MockWebServer server; - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - CurlConnectionManager connectionManager(handler); - - const std::string cv = "aaaaaaaaaaaaaaaa.1"; - ConnectionConfig config; - config.baseCV = cv; - auto connection = connectionManager.MakeConnection(config); - SFSUrlBuilder urlBuilder(SFSCustomUrl(server.GetBaseUrl()), c_instanceId, c_namespace, handler); - - const std::string url = urlBuilder.GetSpecificVersionUrl(c_productName, c_version); - - server.RegisterProduct(c_productName, c_version); - - INFO("First value ends with .0"); - server.RegisterExpectedRequestHeader(HttpHeader::MSCV, cv + ".0"); - connection->Get(url); - - INFO("Value gets incremented on subsequent requests"); - server.RegisterExpectedRequestHeader(HttpHeader::MSCV, cv + ".1"); - connection->Get(url); - - server.RegisterExpectedRequestHeader(HttpHeader::MSCV, cv + ".2"); - connection->Get(url); -} - -TEST("Testing retry behavior") -{ - if (!AreTestOverridesAllowed()) - { - INFO("Skipping. Test overrides not enabled"); - return; - } - - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - MockWebServer server; - CurlConnectionManager connectionManager(handler); - SFSUrlBuilder urlBuilder(SFSCustomUrl(server.GetBaseUrl()), c_instanceId, c_namespace, handler); - - server.RegisterProduct(c_productName, c_version); - const std::string url = urlBuilder.GetSpecificVersionUrl(c_productName, c_version); - - SECTION("Test exponential backoff") - { - auto connection = connectionManager.MakeConnection({}); - - INFO("Sets the retry delay to 50ms to speed up the test"); - ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 50); - - const int retriableError = 503; // ServerBusy - - auto RunTimedGet = [&](bool success = true) -> long long { - std::string out; - auto begin = steady_clock::now(); - if (success) - { - REQUIRE_NOTHROW(out = connection->Get(url)); - REQUIRE_FALSE(out.empty()); - } - else - { - REQUIRE_THROWS_CODE(out = connection->Get(url), HttpServiceNotAvailable); - REQUIRE(out.empty()); - } - auto end = steady_clock::now(); - return duration_cast(end - begin).count(); - }; - - long long allowedTimeDeviation = 200LL; - std::queue forcedHttpErrors({retriableError}); - SECTION("Should take at least 50ms with a single retriable error") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 50LL); - REQUIRE(time < 50LL + allowedTimeDeviation); - } - - forcedHttpErrors.push(retriableError); - SECTION("Should take at least 150ms (50ms + 2*50ms) with two retriable errors") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 150LL); - REQUIRE(time < 150LL + allowedTimeDeviation); - } - - forcedHttpErrors.push(retriableError); - SECTION("Should take at least 300ms (50ms + 2*50ms + 3*50ms) with three retriable errors") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 300LL); - REQUIRE(time < 300LL + allowedTimeDeviation); - } - - forcedHttpErrors.push(retriableError); - SECTION("Should take at least 300ms (50ms + 2*50ms + 3*50ms) with four retriable errors, but fail") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(false /*success*/); - REQUIRE(time >= 300LL); - REQUIRE(time < 300LL + allowedTimeDeviation); - } - } - - SECTION("Test retriable errors with Retry-After headers") - { - auto connection = connectionManager.MakeConnection({}); - - INFO("Sets the retry delay to 200ms to speed up the test"); - ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 200); - - const int retriableError = 503; // ServerBusy - const int retriableError2 = 502; // BadGateway - - auto RunTimedGet = [&]() -> long long { - std::string out; - auto begin = steady_clock::now(); - REQUIRE_NOTHROW(out = connection->Get(url)); - REQUIRE_FALSE(out.empty()); - auto end = steady_clock::now(); - return duration_cast(end - begin).count(); - }; - - auto testForRetryAfterValue = [&](const std::string& retryAfterValue) -> void { - INFO("retryAfterValue = " << retryAfterValue); - - std::unordered_map headersByCode; - headersByCode[retriableError] = {{"Retry-After", retryAfterValue}}; - server.SetResponseHeaders(headersByCode); - - long long allowedTimeDeviation = 200LL; - std::queue forcedHttpErrors({retriableError}); - SECTION("Should take at least 2000ms with a single retriable error, with 2s in Retry-After") - { - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 2000LL); - REQUIRE(time < 2000LL + allowedTimeDeviation); - } - - SECTION( - "Should take at least 2000ms + 200ms*2 (exponential backoff) with two retriable errors, with 2s in Retry-After of the first error") - { - forcedHttpErrors.push(retriableError2); - server.SetForcedHttpErrors(forcedHttpErrors); - const auto time = RunTimedGet(); - REQUIRE(time >= 2400LL); - REQUIRE(time < 2400LL + allowedTimeDeviation); - } - }; - - SECTION("Using seconds") - { - testForRetryAfterValue("2"); // 2s delay - } - SECTION("Using date") - { - const auto time = std::chrono::system_clock::now() + std::chrono::seconds(2); - testForRetryAfterValue(TimestampToHttpDateString(time)); - } - } - - SECTION("Test maxRetries") - { - INFO("Sets the retry delay to 1ms to speed up the test"); - ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 1); - - const int retriableError = 503; // ServerBusy - - SECTION("Should pass with 3 errors") - { - auto connection = connectionManager.MakeConnection({}); - server.SetForcedHttpErrors(std::queue({retriableError, retriableError, retriableError})); - REQUIRE_NOTHROW(connection->Get(url)); - } - - SECTION("Should fail with 4 errors") - { - auto connection = connectionManager.MakeConnection({}); - server.SetForcedHttpErrors( - std::queue({retriableError, retriableError, retriableError, retriableError})); - REQUIRE_THROWS_CODE(connection->Get(url), HttpServiceNotAvailable); - } - - SECTION("Reducing retries to 2") - { - ConnectionConfig config; - config.maxRetries = 2; - auto connection = connectionManager.MakeConnection(config); - - SECTION("Should pass with 2 errors") - { - server.SetForcedHttpErrors(std::queue({retriableError, retriableError})); - REQUIRE_NOTHROW(connection->Get(url)); - } - - SECTION("Should fail with 3 errors") - { - server.SetForcedHttpErrors(std::queue({retriableError, retriableError, retriableError})); - REQUIRE_THROWS_CODE(connection->Get(url), HttpServiceNotAvailable); - } - } - - SECTION("Reducing retries to 0") - { - ConnectionConfig config; - config.maxRetries = 0; - auto connection = connectionManager.MakeConnection(config); - - SECTION("Should pass with no errors") - { - REQUIRE_NOTHROW(connection->Get(url)); - } - - SECTION("Should fail with 1 error") - { - server.SetForcedHttpErrors(std::queue({retriableError})); - REQUIRE_THROWS_CODE(connection->Get(url), HttpServiceNotAvailable); - } - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/functional/details/SFSClientImplTests.cpp b/src/SfsClient/sfs-client/client/tests/functional/details/SFSClientImplTests.cpp deleted file mode 100644 index 1b6fa1b6ea..0000000000 --- a/src/SfsClient/sfs-client/client/tests/functional/details/SFSClientImplTests.cpp +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../mock/MockWebServer.h" -#include "../../util/SFSExceptionMatcher.h" -#include "../../util/TestHelper.h" -#include "SFSClientImpl.h" -#include "connection/Connection.h" -#include "connection/CurlConnectionManager.h" -#include "connection/HttpHeader.h" - -#include - -#include - -#define TEST(...) TEST_CASE("[Functional][SFSClientImplTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; - -namespace -{ -void CheckProduct(const VersionEntity& entity, std::string_view ns, std::string_view name, std::string_view version) -{ - REQUIRE(entity.GetContentType() == ContentType::Generic); - REQUIRE(entity.contentId.nameSpace == ns); - REQUIRE(entity.contentId.name == name); - REQUIRE(entity.contentId.version == version); -} - -void CheckAppProduct(const VersionEntity& entity, std::string_view ns, std::string_view name, std::string_view version) -{ - REQUIRE(entity.GetContentType() == ContentType::App); - auto appEntity = dynamic_cast(entity); - REQUIRE(appEntity.contentId.nameSpace == ns); - REQUIRE(appEntity.contentId.name == name); - REQUIRE(appEntity.contentId.version == version); - REQUIRE_FALSE(appEntity.updateId.empty()); - REQUIRE(appEntity.prerequisites.empty()); -} - -void CheckProducts(const VersionEntities& entities, - std::string_view ns, - const std::set>& nameVersionPairs) -{ - // json arrays don't guarantee order, and they are the underlying structure, so we need to check using sets - std::set> uniqueNameVersionPairs; - for (const auto& entity : entities) - { - REQUIRE(entity->contentId.nameSpace == ns); - uniqueNameVersionPairs.emplace(entity->contentId.name, entity->contentId.version); - } - - for (const auto& nameVersionPair : nameVersionPairs) - { - REQUIRE(uniqueNameVersionPairs.count(nameVersionPair)); - } -} - -void CheckDownloadInfo(const FileEntities& files, const std::string& name) -{ - REQUIRE(files.size() == 2); - REQUIRE(files[0]->GetContentType() == ContentType::Generic); - REQUIRE(files[0]->fileId == (name + ".json")); - REQUIRE(files[0]->url == ("http://localhost/1.json")); - REQUIRE(files[1]->GetContentType() == ContentType::Generic); - REQUIRE(files[1]->fileId == (name + ".bin")); - REQUIRE(files[1]->url == ("http://localhost/2.bin")); -} - -void CheckAppDownloadInfo(const FileEntities& files, const std::string& name) -{ - REQUIRE(files.size() == 2); - REQUIRE(files[0]->GetContentType() == ContentType::App); - REQUIRE(files[0]->fileId == (name + ".json")); - REQUIRE(files[0]->url == ("http://localhost/1.json")); - REQUIRE(files[1]->GetContentType() == ContentType::App); - REQUIRE(files[1]->fileId == (name + ".bin")); - REQUIRE(files[1]->url == ("http://localhost/2.bin")); -} -} // namespace - -TEST("Testing class SFSClientImpl()") -{ - test::MockWebServer server; - const std::string ns = "testNameSpace"; - SFSClientImpl sfsClient({"testAccountId", "testInstanceId", ns, LogCallbackToTest}); - sfsClient.SetCustomBaseUrl(server.GetBaseUrl()); - - const std::string cv = "aaaaaaaaaaaaaaaa.1"; - ConnectionConfig config; - config.baseCV = cv; - auto connection = sfsClient.MakeConnection(config); - server.RegisterExpectedRequestHeader(HttpHeader::UserAgent, GetUserAgentValue()); - - SECTION("Generic products") - { - server.RegisterProduct("productName", "0.0.0.2"); - server.RegisterProduct("productName", "0.0.0.1"); - - SECTION("Testing SFSClientImpl::GetLatestVersion()") - { - server.RegisterExpectedRequestHeader(HttpHeader::ContentType, "application/json"); - std::unique_ptr entity; - - SECTION("No attributes") - { - REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({"productName", {}}, *connection)); - REQUIRE(entity); - CheckProduct(*entity, ns, "productName", "0.0.0.2"); - } - - SECTION("With attributes") - { - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({"productName", attributes}, *connection)); - REQUIRE(entity); - CheckProduct(*entity, ns, "productName", "0.0.0.2"); - } - - SECTION("Wrong product name") - { - REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", {}}, *connection), HttpNotFound); - REQUIRE(!entity); - - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", attributes}, *connection), - HttpNotFound); - REQUIRE(!entity); - } - } - - SECTION("Testing SFSClientImpl::GetLatestVersionBatch()") - { - server.RegisterExpectedRequestHeader(HttpHeader::ContentType, "application/json"); - VersionEntities entities; - - SECTION("No attributes") - { - REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{"productName", {}}}, *connection)); - REQUIRE(!entities.empty()); - CheckProduct(*entities[0], ns, "productName", "0.0.0.2"); - } - - SECTION("With attributes") - { - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{"productName", attributes}}, *connection)); - REQUIRE(!entities.empty()); - CheckProduct(*entities[0], ns, "productName", "0.0.0.2"); - } - - SECTION("Wrong product name") - { - REQUIRE_THROWS_CODE(entities = sfsClient.GetLatestVersionBatch({{"badName", {}}}, *connection), - HttpNotFound); - REQUIRE(entities.empty()); - - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_THROWS_CODE(entities = sfsClient.GetLatestVersionBatch({{"badName", attributes}}, *connection), - HttpNotFound); - REQUIRE(entities.empty()); - } - - SECTION("Multiple unique products") - { - server.RegisterProduct("productName2", "0.0.0.3"); - - REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{"productName", {}}, {"productName2", {}}}, - *connection)); - REQUIRE(entities.size() == 2); - CheckProducts(entities, ns, {{"productName", "0.0.0.2"}, {"productName2", "0.0.0.3"}}); - - server.RegisterProduct("productName3", "0.0.0.4"); - - REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch( - {{"productName", {}}, {"productName2", {}}, {"productName3", {}}}, - *connection)); - REQUIRE(entities.size() == 3); - CheckProducts(entities, - ns, - {{"productName", "0.0.0.2"}, {"productName2", "0.0.0.3"}, {"productName3", "0.0.0.4"}}); - } - - SECTION("Multiple repeated products") - { - REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{"productName", {}}, {"productName", {}}}, - *connection)); - REQUIRE(entities.size() == 1); - CheckProduct(*entities[0], ns, "productName", "0.0.0.2"); - - server.RegisterProduct("productName2", "0.0.0.3"); - - REQUIRE_NOTHROW( - entities = sfsClient.GetLatestVersionBatch( - {{"productName", {}}, {"productName", {}}, {"productName2", {}}, {"productName2", {}}}, - *connection)); - REQUIRE(entities.size() == 2); - CheckProducts(entities, ns, {{"productName", "0.0.0.2"}, {"productName2", "0.0.0.3"}}); - } - - SECTION("Multiple wrong products returns 404") - { - REQUIRE_THROWS_CODE( - entities = sfsClient.GetLatestVersionBatch({{"badName", {}}, {"badName2", {}}}, *connection), - HttpNotFound); - REQUIRE(entities.empty()); - } - - SECTION("Multiple products, one wrong returns 200") - { - REQUIRE_NOTHROW( - entities = sfsClient.GetLatestVersionBatch({{"productName", {}}, {"badName", {}}}, *connection)); - REQUIRE(entities.size() == 1); - CheckProduct(*entities[0], ns, "productName", "0.0.0.2"); - } - } - - SECTION("Testing SFSClientImpl::GetSpecificVersion()") - { - std::unique_ptr entity; - SECTION("Getting 0.0.0.1") - { - REQUIRE_NOTHROW(entity = sfsClient.GetSpecificVersion("productName", "0.0.0.1", *connection)); - REQUIRE(entity); - CheckProduct(*entity, ns, "productName", "0.0.0.1"); - } - - SECTION("Getting 0.0.0.2") - { - REQUIRE_NOTHROW(entity = sfsClient.GetSpecificVersion("productName", "0.0.0.2", *connection)); - REQUIRE(entity); - CheckProduct(*entity, ns, "productName", "0.0.0.2"); - } - - SECTION("Wrong product name") - { - REQUIRE_THROWS_CODE(entity = sfsClient.GetSpecificVersion("badName", "0.0.0.2", *connection), - HttpNotFound); - REQUIRE(!entity); - } - - SECTION("Wrong version") - { - REQUIRE_THROWS_CODE(entity = sfsClient.GetSpecificVersion("productName", "0.0.0.3", *connection), - HttpNotFound); - REQUIRE(!entity); - } - } - - SECTION("Testing SFSClientImpl::GetDownloadInfo()") - { - server.RegisterExpectedRequestHeader(HttpHeader::ContentType, "application/json"); - FileEntities files; - - SECTION("Getting 0.0.0.1") - { - REQUIRE_NOTHROW(files = sfsClient.GetDownloadInfo("productName", "0.0.0.1", *connection)); - REQUIRE(!files.empty()); - CheckDownloadInfo(files, "productName"); - } - - SECTION("Getting 0.0.0.2") - { - REQUIRE_NOTHROW(files = sfsClient.GetDownloadInfo("productName", "0.0.0.2", *connection)); - REQUIRE(!files.empty()); - CheckDownloadInfo(files, "productName"); - } - - SECTION("Wrong product name") - { - REQUIRE_THROWS_CODE(files = sfsClient.GetDownloadInfo("badName", "0.0.0.2", *connection), HttpNotFound); - REQUIRE(files.empty()); - } - - SECTION("Wrong version") - { - REQUIRE_THROWS_CODE(files = sfsClient.GetDownloadInfo("productName", "0.0.0.3", *connection), - HttpNotFound); - REQUIRE(files.empty()); - } - } - } - - SECTION("App products") - { - server.RegisterAppProduct("productName", "0.0.0.2", {}); - server.RegisterAppProduct("productName", "0.0.0.1", {}); - - SECTION("Testing SFSClientImpl::GetLatestVersion()") - { - server.RegisterExpectedRequestHeader(HttpHeader::ContentType, "application/json"); - std::unique_ptr entity; - - SECTION("No attributes") - { - REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({"productName", {}}, *connection)); - REQUIRE(entity); - CheckAppProduct(*entity, ns, "productName", "0.0.0.2"); - } - - SECTION("With attributes") - { - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({"productName", attributes}, *connection)); - REQUIRE(entity); - CheckAppProduct(*entity, ns, "productName", "0.0.0.2"); - } - - SECTION("Wrong product name") - { - REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", {}}, *connection), HttpNotFound); - REQUIRE(!entity); - - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", attributes}, *connection), - HttpNotFound); - REQUIRE(!entity); - } - } - - SECTION("Testing SFSClientImpl::GetDownloadInfo()") - { - server.RegisterExpectedRequestHeader(HttpHeader::ContentType, "application/json"); - FileEntities files; - - SECTION("Getting 0.0.0.1") - { - REQUIRE_NOTHROW(files = sfsClient.GetDownloadInfo("productName", "0.0.0.1", *connection)); - REQUIRE(!files.empty()); - CheckAppDownloadInfo(files, "productName"); - } - - SECTION("Getting 0.0.0.2") - { - REQUIRE_NOTHROW(files = sfsClient.GetDownloadInfo("productName", "0.0.0.2", *connection)); - REQUIRE(!files.empty()); - CheckAppDownloadInfo(files, "productName"); - } - - SECTION("Wrong product name") - { - REQUIRE_THROWS_CODE(files = sfsClient.GetDownloadInfo("badName", "0.0.0.2", *connection), HttpNotFound); - REQUIRE(files.empty()); - } - - SECTION("Wrong version") - { - REQUIRE_THROWS_CODE(files = sfsClient.GetDownloadInfo("productName", "0.0.0.3", *connection), - HttpNotFound); - REQUIRE(files.empty()); - } - } - } - - REQUIRE(server.Stop() == Result::Success); -} diff --git a/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp b/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp deleted file mode 100644 index be085ac05d..0000000000 --- a/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp +++ /dev/null @@ -1,698 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "MockWebServer.h" - -#include "../util/TestHelper.h" -#include "ErrorHandling.h" -#include "ServerCommon.h" -#include "Util.h" -#include "connection/HttpHeader.h" - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#endif - -#include - -#include -#include -#include -#include -#include - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::util; -using namespace SFS::test; -using namespace SFS::test::details; -using json = nlohmann::json; - -namespace -{ - -struct App -{ - std::string version; - std::vector prerequisites; -}; - -struct AppCmp -{ - bool operator()(App a, App b) const - { - return a.version < b.version; - } -}; - -json GenerateContentIdJsonObject(const std::string& name, const std::string& latestVersion, const std::string& ns) -{ - // { - // "ContentId": { - // "Namespace": , - // "Name": , - // "Version": - // } - // } - - return {{"ContentId", {{"Namespace", ns}, {"Name", name}, {"Version", latestVersion}}}}; -} - -json GenerateGetAppVersionJsonObject(const std::string& name, const App& app, const std::string& ns) -{ - // { - // "ContentId": { - // "Namespace": , - // "Name": , - // "Version": - // }, - // "UpdateId": "", - // "Prerequisites": [ - // { - // "Namespace": "", - // "Name": "", - // "Version": "" - // } - // ] - // } - - json prereqs = json::array(); - for (const auto& prereq : app.prerequisites) - { - prereqs.push_back({{"Namespace", ns}, {"Name", prereq.name}, {"Version", prereq.version}}); - } - return {{"ContentId", {{"Namespace", ns}, {"Name", name}, {"Version", app.version}}}, - {"UpdateId", "123"}, - {"Prerequisites", prereqs}}; -} - -json GeneratePostDownloadInfo(const std::string& name) -{ - // [ - // { - // "Url": , - // "FileId": , - // "SizeInBytes": , - // "Hashes": { - // "Sha1": , - // "Sha256": - // }, - // "DeliveryOptimization": { - // "CatalogId": , - // "Properties": { - // "IntegrityCheckInfo": { - // "PiecesHashFileUrl": , - // "HashOfHashes": - // } - // } - // } - // }, - // ... - // ] - - // Generating DeliveryOptimizationData to simulate the server response, but it's not being parsed by the Client - - json response; - response = json::array(); - response.push_back({{"Url", "http://localhost/1.json"}, - {"FileId", name + ".json"}, - {"SizeInBytes", 100}, - {"Hashes", {{"Sha1", "123"}, {"Sha256", "456"}}}}); - response[0]["DeliveryOptimization"] = {{"CatalogId", "789"}}; - response[0]["DeliveryOptimization"]["Properties"] = { - {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/1.json"}, {"HashOfHashes", "abc"}}}}; - - response.push_back({{"Url", "http://localhost/2.bin"}, - {"FileId", name + ".bin"}, - {"SizeInBytes", 200}, - {"Hashes", {{"Sha1", "421"}, {"Sha256", "132"}}}}); - response[1]["DeliveryOptimization"] = {{"CatalogId", "14"}}; - response[1]["DeliveryOptimization"]["Properties"] = { - {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/2.bin"}, {"HashOfHashes", "abcd"}}}}; - return response; -} - -json GeneratePostAppDownloadInfo(const std::string& name) -{ - // [ - // { - // "Url": , - // "FileId": , - // "SizeInBytes": , - // "Hashes": { - // "Sha1": , - // "Sha256": - // }, - // "DeliveryOptimization": { - // "CatalogId": , - // "Properties": { - // "IntegrityCheckInfo": { - // "PiecesHashFileUrl": , - // "HashOfHashes": - // } - // } - // }, - // "ApplicabilityDetails": { - // "Architectures": [ - // "" - // ], - // "PlatformApplicabilityForPackage": [ - // "" - // ] - // }, - // "FileMoniker": "" - // } - // ] - - // Generating DeliveryOptimizationData to simulate the server response, but it's not being parsed by the Client - - json response; - response = json::array(); - response.push_back({{"Url", "http://localhost/1.json"}, - {"FileId", name + ".json"}, - {"SizeInBytes", 100}, - {"Hashes", {{"Sha1", "123"}, {"Sha256", "456"}}}}); - response[0]["DeliveryOptimization"] = {{"CatalogId", "789"}}; - response[0]["DeliveryOptimization"]["Properties"] = { - {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/1.json"}, {"HashOfHashes", "abc"}}}}; - response[0]["ApplicabilityDetails"] = {{"Architectures", {"x86"}}, - {"PlatformApplicabilityForPackage", {"Windows"}}}; - response[0]["FileMoniker"] = "1.json"; - - response.push_back({{"Url", "http://localhost/2.bin"}, - {"FileId", name + ".bin"}, - {"SizeInBytes", 200}, - {"Hashes", {{"Sha1", "421"}, {"Sha256", "132"}}}}); - response[1]["DeliveryOptimization"] = {{"CatalogId", "14"}}; - response[1]["DeliveryOptimization"]["Properties"] = { - {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/2.bin"}, {"HashOfHashes", "abcd"}}}}; - response[1]["ApplicabilityDetails"] = {{"Architectures", {"amd64"}}, - {"PlatformApplicabilityForPackage", {"Linux"}}}; - response[1]["FileMoniker"] = "2.bin"; - - return response; -} - -void CheckApiVersion(const httplib::Request& req, std::string_view apiVersion) -{ - if (util::AreNotEqualI(req.path_params.at("apiVersion"), apiVersion)) - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } -} -} // namespace - -namespace SFS::test::details -{ -class MockWebServerImpl : public BaseServerImpl -{ - public: - MockWebServerImpl() = default; - ~MockWebServerImpl() = default; - - MockWebServerImpl(const MockWebServerImpl&) = delete; - MockWebServerImpl& operator=(const MockWebServerImpl&) = delete; - - void RegisterProduct(std::string&& name, std::string&& version); - void RegisterAppProduct(std::string&& name, std::string&& version, std::vector&& prerequisites); - void RegisterExpectedRequestHeader(std::string&& header, std::string&& value); - void SetForcedHttpErrors(std::queue forcedErrors); - void SetResponseHeaders(std::unordered_map headersByCode); - - private: - void ConfigureRequestHandlers() override; - std::string GetLogIdentifier() override; - - void ConfigurePostLatestVersion(); - void ConfigurePostLatestVersionBatch(); - void ConfigureGetSpecificVersion(); - void ConfigurePostDownloadInfo(); - - void RunHttpCallback(const httplib::Request& req, - httplib::Response& res, - const std::string& methodName, - const std::string& apiVersion, - const std::function& callback); - void CheckRequestHeaders(const httplib::Request& req); - - using VersionList = std::set; - std::unordered_map m_products; - - using AppList = std::set; - std::unordered_map m_appProducts; - - std::unordered_map m_expectedRequestHeaders; - std::queue m_forcedHttpErrors; - std::unordered_map m_headersByCode; -}; -} // namespace SFS::test::details - -MockWebServer::MockWebServer() -{ - m_impl = std::make_unique(); - m_impl->Start(); -} - -MockWebServer::~MockWebServer() -{ - const auto ret = Stop(); - if (!ret) - { - TEST_UNSCOPED_INFO("Failed to stop: " + std::string(ToString(ret.GetCode()))); - } -} - -Result MockWebServer::Stop() -{ - return m_impl->Stop(); -} - -std::string MockWebServer::GetBaseUrl() const -{ - return m_impl->GetUrl(); -} - -void MockWebServer::RegisterProduct(std::string name, std::string version) -{ - m_impl->RegisterProduct(std::move(name), std::move(version)); -} - -void MockWebServer::RegisterAppProduct(std::string name, - std::string version, - std::vector prerequisites) -{ - m_impl->RegisterAppProduct(std::move(name), std::move(version), std::move(prerequisites)); -} - -void MockWebServer::RegisterExpectedRequestHeader(HttpHeader header, std::string value) -{ - std::string headerName = ToString(header); - m_impl->RegisterExpectedRequestHeader(std::move(headerName), std::move(value)); -} - -void MockWebServer::SetForcedHttpErrors(std::queue forcedErrors) -{ - m_impl->SetForcedHttpErrors(std::move(forcedErrors)); -} - -void MockWebServer::SetResponseHeaders(std::unordered_map headersByCode) -{ - m_impl->SetResponseHeaders(std::move(headersByCode)); -} - -void MockWebServerImpl::ConfigureRequestHandlers() -{ - ConfigurePostLatestVersion(); - ConfigurePostLatestVersionBatch(); - ConfigureGetSpecificVersion(); - ConfigurePostDownloadInfo(); -} - -std::string MockWebServerImpl::GetLogIdentifier() -{ - return "MockWebServer"; -} - -void MockWebServerImpl::ConfigurePostLatestVersion() -{ - // Path: /api//contents//namespaces//names//versions/latest?action=select - const std::string pattern = "/api/:apiVersion/contents/:instanceId/namespaces/:ns/names/:name/versions/latest"; - m_server.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) { - RunHttpCallback(req, res, "PostLatestVersion", "v2", [&](const httplib::Request& req, httplib::Response& res) { - // TODO: Ignoring instanceId for now - - if (!req.has_param("action") || util::AreNotEqualI(req.get_param_value("action"), "select")) - { - // TODO: SFS might throw a different error when the query string is unexpected - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - // Checking body has expected format, but won't use it for the response - { - if (req.body.empty()) - { - throw StatusCodeException(httplib::StatusCode::BadRequest_400); - } - - json body; - try - { - body = json::parse(req.body); - } - catch (const json::parse_error& ex) - { - BUFFER_LOG("JSON parse error: " + std::string(ex.what())); - throw StatusCodeException(httplib::StatusCode::BadRequest_400); - } - - // The GetLatestVersion API expects an object as a body, with a "TargetingAttributes" object element. - if (!body.is_object() || !body.contains("TargetingAttributes") || - !body["TargetingAttributes"].is_object()) - { - throw StatusCodeException(httplib::StatusCode::BadRequest_400); - } - } - - const std::string ns = req.path_params.at("ns"); - - json response; - const std::string& name = req.path_params.at("name"); - if (auto it = m_products.find(name); it != m_products.end()) - { - const auto& versions = it->second; - if (versions.empty()) - { - throw StatusCodeException(httplib::StatusCode::InternalServerError_500); - } - - const auto& latestVersion = *versions.rbegin(); - response = GenerateContentIdJsonObject(name, latestVersion, ns); - } - else if (auto appIt = m_appProducts.find(name); appIt != m_appProducts.end()) - { - const auto& appList = appIt->second; - if (appList.empty()) - { - throw StatusCodeException(httplib::StatusCode::InternalServerError_500); - } - - const auto& latestApp = *appList.rbegin(); - response = GenerateGetAppVersionJsonObject(name, latestApp, ns); - } - else - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - res.set_content(response.dump(), "application/json"); - }); - }); -} - -void MockWebServerImpl::ConfigurePostLatestVersionBatch() -{ - // Path: /api//contents//namespaces//names?action=BatchUpdates - const std::string pattern = "/api/:apiVersion/contents/:instanceId/namespaces/:ns/names"; - m_server.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) { - RunHttpCallback( - req, - res, - "PostLatestVersionBatch", - "v2", - [&](const httplib::Request& req, httplib::Response& res) { - // TODO: Ignoring instanceId for now - - if (!req.has_param("action") || util::AreNotEqualI(req.get_param_value("action"), "BatchUpdates")) - { - // TODO: SFS might throw a different error when the query string is unexpected - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - if (req.body.empty()) - { - throw StatusCodeException(httplib::StatusCode::BadRequest_400); - } - - json body; - try - { - body = json::parse(req.body); - } - catch (const json::parse_error& ex) - { - BUFFER_LOG("JSON parse error: " + std::string(ex.what())); - throw StatusCodeException(httplib::StatusCode::BadRequest_400); - } - - // The BatchUpdates API returns an array of objects, each with a "Product" key. - // If repeated, the same product is only returned once. - // TODO: We are ignoring the TargetingAttributes for now. - if (!body.is_array()) - { - throw StatusCodeException(httplib::StatusCode::BadRequest_400); - } - - // Iterate over the array and collect the unique products - std::unordered_map requestedProducts; - for (const auto& productRequest : body) - { - if (!productRequest.is_object() || !productRequest.contains("Product") || - !productRequest["Product"].is_string() || !productRequest.contains("TargetingAttributes")) - { - throw StatusCodeException(httplib::StatusCode::BadRequest_400); - } - if (requestedProducts.count(productRequest["Product"])) - { - continue; - } - requestedProducts.emplace(productRequest["Product"], productRequest["TargetingAttributes"]); - } - - // If at least one product exists, we will return a 200 OK with that. Non-existing products are ignored. - // Otherwise, a 404 is sent. - json response = json::array(); - for (const auto& [name, _] : requestedProducts) - { - auto it = m_products.find(name); - if (it == m_products.end()) - { - continue; - } - - const VersionList& versions = it->second; - if (versions.empty()) - { - throw StatusCodeException(httplib::StatusCode::InternalServerError_500); - } - - const std::string ns = req.path_params.at("ns"); - const auto& latestVersion = *versions.rbegin(); - - response.push_back(GenerateContentIdJsonObject(name, latestVersion, ns)); - } - - if (response.empty()) - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - res.set_content(response.dump(), "application/json"); - }); - }); -} - -void MockWebServerImpl::ConfigureGetSpecificVersion() -{ - // Path: /api//contents//namespaces//names//versions/ - const std::string pattern = "/api/:apiVersion/contents/:instanceId/namespaces/:ns/names/:name/versions/:version"; - m_server.Get(pattern, [&](const httplib::Request& req, httplib::Response& res) { - RunHttpCallback(req, res, "GetSpecificVersion", "v2", [&](const httplib::Request& req, httplib::Response& res) { - // TODO: Ignoring instanceId for now - - const std::string& name = req.path_params.at("name"); - auto it = m_products.find(name); - if (it == m_products.end()) - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - const VersionList& versions = it->second; - if (versions.empty()) - { - throw StatusCodeException(httplib::StatusCode::InternalServerError_500); - } - - // TODO: Are apps suppported? - - const std::string& version = req.path_params.at("version"); - if (version.empty() || !versions.count(version)) - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - const std::string ns = req.path_params.at("ns"); - - res.set_content(GenerateContentIdJsonObject(name, version, ns).dump(), "application/json"); - }); - }); -} - -void MockWebServerImpl::ConfigurePostDownloadInfo() -{ - // Path: - // /api//contents//namespaces//names//versions//files?action=GenerateDownloadInfo - const std::string pattern = - "/api/:apiVersion/contents/:instanceId/namespaces/:ns/names/:name/versions/:version/files"; - m_server.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) { - RunHttpCallback(req, res, "PostDownloadInfo", "v2", [&](const httplib::Request& req, httplib::Response& res) { - // TODO: Ignoring instanceId and ns for now - - if (!req.has_param("action") || util::AreNotEqualI(req.get_param_value("action"), "GenerateDownloadInfo")) - { - // TODO: SFS might throw a different error when the query string is unexpected - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - const std::string& version = req.path_params.at("version"); - if (version.empty()) - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - json response; - const std::string& name = req.path_params.at("name"); - if (auto it = m_products.find(name); it != m_products.end()) - { - const auto& versions = it->second; - if (versions.empty()) - { - throw StatusCodeException(httplib::StatusCode::InternalServerError_500); - } - - if (!versions.count(version)) - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - // Response is a dummy, doesn't use the version above - response = GeneratePostDownloadInfo(name); - } - else if (auto appIt = m_appProducts.find(name); appIt != m_appProducts.end()) - { - const auto& appList = appIt->second; - if (appList.empty()) - { - throw StatusCodeException(httplib::StatusCode::InternalServerError_500); - } - - auto app = std::find_if(appList.begin(), appList.end(), [&](const App& app) { - return app.version == version; - }); - if (app == appList.end()) - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - // Response is a dummy, doesn't use the version above - response = GeneratePostAppDownloadInfo(name); - } - else - { - throw StatusCodeException(httplib::StatusCode::NotFound_404); - } - - res.set_content(response.dump(), "application/json"); - }); - }); -} - -void MockWebServerImpl::RunHttpCallback(const httplib::Request& req, - httplib::Response& res, - const std::string& methodName, - const std::string& apiVersion, - const std::function& callback) -{ - if (m_forcedHttpErrors.size() > 0) - { - res.status = m_forcedHttpErrors.front(); - m_forcedHttpErrors.pop(); - - BUFFER_LOG("Forcing HTTP error: " + std::to_string(res.status)); - } - else - { - try - { - BUFFER_LOG("Matched " + methodName); - CheckApiVersion(req, apiVersion); - CheckRequestHeaders(req); - callback(req, res); - res.status = httplib::StatusCode::OK_200; - } - catch (const StatusCodeException& ex) - { - res.status = ex.GetStatusCode(); - } - catch (const std::exception&) - { - res.status = httplib::StatusCode::InternalServerError_500; - } - catch (...) - { - res.status = httplib::StatusCode::InternalServerError_500; - } - } - - if (m_headersByCode.count(res.status) > 0) - { - BUFFER_LOG("HTTP code " + std::to_string(res.status) + " has response headers to be sent"); - for (const auto& header : m_headersByCode[res.status]) - { - BUFFER_LOG("Adding header [" + header.first + "] with value [" + header.second + "]"); - res.set_header(header.first, header.second); - } - } -} - -void MockWebServerImpl::CheckRequestHeaders(const httplib::Request& req) -{ - for (const auto& header : m_expectedRequestHeaders) - { - std::optional errorMessage; - if (!req.has_header(header.first)) - { - errorMessage = "Expected header [" + header.first + "] not found"; - } - else if (util::AreNotEqualI(req.get_header_value(header.first), header.second)) - { - errorMessage = "Header [" + header.first + "] with value [" + req.get_header_value(header.first) + - "] does not match the expected value [" + header.second + "]"; - } - - if (errorMessage) - { - BUFFER_LOG(*errorMessage); - throw std::runtime_error(errorMessage->c_str()); - } - } -} - -void MockWebServerImpl::RegisterProduct(std::string&& name, std::string&& version) -{ - m_products[std::move(name)].emplace(std::move(version)); -} - -void MockWebServerImpl::RegisterAppProduct(std::string&& name, - std::string&& version, - std::vector&& prerequisites) -{ - for (const auto& prereq : prerequisites) - { - m_appProducts[prereq.name].emplace(App{prereq.version, {}}); - } - m_appProducts[std::move(name)].emplace(App{std::move(version), std::move(prerequisites)}); -} - -void MockWebServerImpl::RegisterExpectedRequestHeader(std::string&& header, std::string&& value) -{ - if (auto it = m_expectedRequestHeaders.find(header); it != m_expectedRequestHeaders.end()) - { - it->second = std::move(value); - return; - } - else - { - m_expectedRequestHeaders.emplace(std::move(header), std::move(value)); - } -} - -void MockWebServerImpl::SetForcedHttpErrors(std::queue forcedErrors) -{ - m_forcedHttpErrors = std::move(forcedErrors); -} - -void MockWebServerImpl::SetResponseHeaders(std::unordered_map headersByCode) -{ - m_headersByCode = std::move(headersByCode); -} diff --git a/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.h b/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.h deleted file mode 100644 index 3c05b3fd6e..0000000000 --- a/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" - -#include -#include -#include -#include - -namespace SFS::details -{ -enum class HttpHeader; -} - -namespace SFS::test -{ -namespace details -{ -class MockWebServerImpl; -} - -using HttpCode = int; -using HeaderMap = std::unordered_map; - -struct MockPrerequisite -{ - std::string name; - std::string version; -}; - -class MockWebServer -{ - public: - MockWebServer(); - ~MockWebServer(); - - MockWebServer(const MockWebServer&) = delete; - MockWebServer& operator=(const MockWebServer&) = delete; - - [[nodiscard]] Result Stop(); - - std::string GetBaseUrl() const; - - /// @brief Registers a product with the server. Will fill the other data with gibberish for testing purposes - void RegisterProduct(std::string name, std::string version); - - /// @brief Registers an app with the server. Will fill the other data with gibberish for testing purposes - void RegisterAppProduct(std::string name, std::string version, std::vector prerequisites); - - /// @brief Registers the expectation of a given header to the present in the request - void RegisterExpectedRequestHeader(SFS::details::HttpHeader header, std::string value); - - /** - * @brief Registers a sequence of HTTP error codes that will be sent by the server in the order in which they are - * passed. - */ - void SetForcedHttpErrors(std::queue forcedErrors); - - /// @brief Registers a set of headers that will be sent depending on the HTTP code - void SetResponseHeaders(std::unordered_map headersByCode); - - private: - std::unique_ptr m_impl; -}; -} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.cpp b/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.cpp deleted file mode 100644 index 2ee330a338..0000000000 --- a/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ProxyServer.h" - -#include "../util/TestHelper.h" -#include "ErrorHandling.h" -#include "ReportingHandler.h" -#include "ServerCommon.h" -#include "UrlBuilder.h" - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; -using namespace SFS::test::details; - -namespace SFS::test::details -{ -class ProxyServerImpl : public BaseServerImpl -{ - private: - void ConfigureRequestHandlers() override; - std::string GetLogIdentifier() override; -}; -} // namespace SFS::test::details - -ProxyServer::ProxyServer() -{ - m_impl = std::make_unique(); - m_impl->Start(); -} - -ProxyServer::~ProxyServer() -{ - const auto ret = Stop(); - if (!ret) - { - TEST_UNSCOPED_INFO("Failed to stop: " + std::string(ToString(ret.GetCode()))); - } -} - -Result ProxyServer::Stop() -{ - return m_impl->Stop(); -} - -std::string ProxyServer::GetBaseUrl() const -{ - return m_impl->GetUrl(); -} - -void ProxyServerImpl::ConfigureRequestHandlers() -{ - auto HandleRequest = [&](const httplib::Request& req, httplib::Response& res) { - // As a proxy, we'll parse the URL and Path/Query so we can reuse them in httplib::Client - ReportingHandler handler; - UrlBuilder urlBuilder(req.target.c_str(), handler); - const std::string path = urlBuilder.GetPath(); - const std::string query = urlBuilder.GetQuery(); - urlBuilder.ResetPath().ResetQuery(); - - // URL may come back from UrlBuilder with a final /, which doesn't work with httplib::Client, so we remove it - auto url = urlBuilder.GetUrl(); - if (url.at(url.size() - 1) == '/') - { - url.pop_back(); - } - const std::string pathAndQuery = path + (query.empty() ? "" : ("?" + query)); - httplib::Client cli(url); - httplib::Result result; - if (req.method == "GET") - { - result = cli.Get(pathAndQuery, req.headers); - } - else if (req.method == "POST") - { - const auto length = std::stoi(req.get_header_value("Content-Length")); - result = - cli.Post(pathAndQuery, req.headers, req.body.c_str(), length, req.get_header_value("Content-Type")); - } - - if (!result) - { - BUFFER_LOG("Client error: " + to_string(result.error())); - throw StatusCodeException(httplib::StatusCode::InternalServerError_500); - } - res = result.value(); - }; - - m_server.Get(".*", HandleRequest); - m_server.Post(".*", HandleRequest); -} - -std::string ProxyServerImpl::GetLogIdentifier() -{ - return "ProxyServer"; -} diff --git a/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.h b/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.h deleted file mode 100644 index eb833b2985..0000000000 --- a/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" - -#include - -namespace SFS::test -{ -namespace details -{ -class ProxyServerImpl; -} - -// Proxy Server implementation that redirects GET and POST requests directly -class ProxyServer -{ - public: - ProxyServer(); - ~ProxyServer(); - - ProxyServer(const ProxyServer&) = delete; - ProxyServer& operator=(const ProxyServer&) = delete; - - Result Stop(); - - std::string GetBaseUrl() const; - - private: - std::unique_ptr m_impl; -}; -} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.cpp b/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.cpp deleted file mode 100644 index bdf3bb2d3d..0000000000 --- a/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ServerCommon.h" - -#include "../util/TestHelper.h" -#include "Result.h" - -using SFS::test::BufferedLogData; -using SFS::test::StatusCodeException; -using SFS::test::details::BaseServerImpl; -using namespace SFS; -using namespace std::string_literals; - -static constexpr const char* c_listenHostName = "localhost"; - -static std::string ToString(httplib::StatusCode status) -{ - return std::to_string(status) + " " + std::string(httplib::status_message(status)); -} - -StatusCodeException::StatusCodeException(httplib::StatusCode status) : m_status(status), m_message(::ToString(m_status)) -{ -} - -const char* StatusCodeException::what() const noexcept -{ - return m_message.c_str(); -} - -httplib::StatusCode StatusCodeException::GetStatusCode() const -{ - return m_status; -} - -static SFS::LogData ToLogData(const BufferedLogData& data) -{ - return {LogSeverity::Info, data.message.c_str(), data.file.c_str(), data.line, data.function.c_str(), data.time}; -} - -void BaseServerImpl::Start() -{ - ConfigureServerSettings(); - ConfigureRequestHandlers(); - - m_port = m_server.bind_to_any_port(c_listenHostName); - m_listenerThread = std::thread([&]() { m_server.listen_after_bind(); }); -} - -void BaseServerImpl::ConfigureServerSettings() -{ - m_server.set_logger([&](const httplib::Request& req, const httplib::Response& res) { - BUFFER_LOG("Request: " + req.method + " " + req.path + " " + req.version); - BUFFER_LOG("Request Body: " + req.body); - - BUFFER_LOG("Response: " + res.version + " " + ::ToString(static_cast(res.status)) + " " + - res.reason); - BUFFER_LOG("Response body: " + res.body); - }); - - m_server.set_exception_handler([&](const httplib::Request&, httplib::Response& res, std::exception_ptr ep) { - try - { - std::rethrow_exception(ep); - } - catch (std::exception& e) - { - m_lastException = Result(Result::HttpUnexpected, e.what()); - } - catch (...) - { - m_lastException = Result(Result::HttpUnexpected, "Unknown Exception"); - } - - ProcessBufferedLogs(); - - res.status = httplib::StatusCode::InternalServerError_500; - }); - - // Keeping this interval to a minimum ensures tests run quicker - m_server.set_keep_alive_timeout(1); // 1 second -} - -void BaseServerImpl::BufferLog(const BufferedLogData& data) -{ - std::lock_guard guard(m_logMutex); - m_bufferedLog.push_back(data); -} - -BufferedLogData BaseServerImpl::BuildBufferedLogData(const std::string& message, - const char* file, - unsigned line, - const char* function) -{ - return BufferedLogData{GetLogIdentifier() + ": " + message, file, line, function, std::chrono::system_clock::now()}; -} - -void BaseServerImpl::ProcessBufferedLogs() -{ - for (const auto& data : m_bufferedLog) - { - LogCallbackToTest(ToLogData(data)); - } - m_bufferedLog.clear(); -} - -Result BaseServerImpl::Stop() -{ - if (m_listenerThread.joinable()) - { - m_server.stop(); - m_listenerThread.join(); - } - ProcessBufferedLogs(); - return m_lastException.value_or(Result::Success); -} - -std::string BaseServerImpl::GetUrl() const -{ - return "http://"s + c_listenHostName + ":"s + std::to_string(m_port); -} diff --git a/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.h b/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.h deleted file mode 100644 index bd3cca9fcd..0000000000 --- a/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Logging.h" -#include "Result.h" - -#include - -#include -#include -#include - -namespace SFS::test -{ -class StatusCodeException : public std::exception -{ - public: - StatusCodeException(httplib::StatusCode status); - - const char* what() const noexcept override; - - httplib::StatusCode GetStatusCode() const; - - private: - httplib::StatusCode m_status; - std::string m_message; -}; - -#define BUILD_BUFFERED_LOG_DATA(message) BuildBufferedLogData(message, __FILE__, __LINE__, __FUNCTION__) - -#define BUFFER_LOG(message) BufferLog(BUILD_BUFFERED_LOG_DATA(message)) - -struct BufferedLogData -{ - std::string message; - std::string file; - unsigned line; - std::string function; - std::chrono::time_point time; -}; - -namespace details -{ -class BaseServerImpl -{ - public: - BaseServerImpl() = default; - ~BaseServerImpl() = default; - - BaseServerImpl(const BaseServerImpl&) = delete; - BaseServerImpl& operator=(const BaseServerImpl&) = delete; - - void Start(); - Result Stop(); - - std::string GetUrl() const; - - protected: - void ConfigureServerSettings(); - virtual void ConfigureRequestHandlers() = 0; - - virtual std::string GetLogIdentifier() = 0; - - void BufferLog(const BufferedLogData& data); - BufferedLogData BuildBufferedLogData(const std::string& message, - const char* file, - unsigned line, - const char* function); - void ProcessBufferedLogs(); - - httplib::Server m_server; - int m_port{-1}; - - std::optional m_lastException; - - std::thread m_listenerThread; - - std::vector m_bufferedLog; - std::mutex m_logMutex; -}; -} // namespace details -} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/client/tests/unit/AppContentTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/AppContentTests.cpp deleted file mode 100644 index 6cf452dd4c..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/AppContentTests.cpp +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentUtil.h" -#include "sfsclient/AppContent.h" - -#include - -#define TEST(...) TEST_CASE("[AppContentTests] " __VA_ARGS__) -#define TEST_SCENARIO(...) TEST_CASE("[AppContentTests] Scenario: " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::contentutil; - -namespace -{ -std::unique_ptr GetContentId(const std::string& nameSpace, - const std::string& name, - const std::string& version) -{ - std::unique_ptr contentId; - REQUIRE(ContentId::Make(nameSpace, name, version, contentId) == Result::Success); - REQUIRE(contentId != nullptr); - return contentId; -} - -std::unique_ptr GetAppFile(const std::string& fileId, - const std::string& url, - uint64_t sizeInBytes, - const std::unordered_map& hashes, - std::vector architectures, - std::vector platformApplicabilityForPackage, - std::string fileMoniker) -{ - std::unique_ptr file; - REQUIRE(AppFile::Make(fileId, - url, - sizeInBytes, - hashes, - architectures, - platformApplicabilityForPackage, - fileMoniker, - file) == Result::Success); - REQUIRE(file != nullptr); - return file; -} - -std::unique_ptr GetPrerequisiteContent(const std::string& contentNameSpace, - const std::string& contentName, - const std::string& contentVersion, - const std::vector& files) -{ - std::unique_ptr contentId = GetContentId(contentNameSpace, contentName, contentVersion); - std::vector clonedFiles; - for (auto& file : files) - { - clonedFiles.push_back(std::move(*GetAppFile(file.GetFileId(), - file.GetUrl(), - file.GetSizeInBytes(), - file.GetHashes(), - file.GetApplicabilityDetails().GetArchitectures(), - file.GetApplicabilityDetails().GetPlatformApplicabilityForPackage(), - file.GetFileMoniker()))); - } - - std::unique_ptr content; - REQUIRE(AppPrerequisiteContent::Make(std::move(contentId), std::move(clonedFiles), content) == Result::Success); - REQUIRE(content != nullptr); - return content; -} - -std::unique_ptr GetAppContent(const std::string& contentNameSpace, - const std::string& contentName, - const std::string& contentVersion, - const std::string& updateId, - const std::vector& prerequisites, - const std::vector& files) -{ - std::unique_ptr contentId = GetContentId(contentNameSpace, contentName, contentVersion); - std::vector clonedPreqs; - for (const auto& prereq : prerequisites) - { - clonedPreqs.push_back(std::move(*GetPrerequisiteContent(prereq.GetContentId().GetNameSpace(), - prereq.GetContentId().GetName(), - prereq.GetContentId().GetVersion(), - prereq.GetFiles()))); - } - - std::vector clonedFiles; - for (auto& file : files) - { - clonedFiles.push_back(std::move(*GetAppFile(file.GetFileId(), - file.GetUrl(), - file.GetSizeInBytes(), - file.GetHashes(), - file.GetApplicabilityDetails().GetArchitectures(), - file.GetApplicabilityDetails().GetPlatformApplicabilityForPackage(), - file.GetFileMoniker()))); - } - - std::unique_ptr appContent; - REQUIRE( - AppContent::Make(std::move(contentId), updateId, std::move(clonedPreqs), std::move(clonedFiles), appContent) == - Result::Success); - REQUIRE(appContent != nullptr); - return appContent; -} -} // namespace - -TEST("Testing AppContent::Make()") -{ - const std::string contentNameSpace{"myNameSpace"}; - const std::string contentName{"myName"}; - const std::string contentVersion{"myVersion"}; - - std::unique_ptr file1 = GetAppFile("fileId1", - "url1", - 1 /*sizeInBytes*/, - {{HashType::Sha1, "sha1"}}, - {Architecture::Amd64}, - {"myPlatformApplicabilityForPackage1"}, - "fileMoniker1"); - std::unique_ptr file2 = GetAppFile("fileId2", - "url2", - 1 /*sizeInBytes*/, - {{HashType::Sha256, "sha256"}}, - {Architecture::Amd64}, - {"myPlatformApplicabilityForPackage1"}, - "fileMoniker1"); - - std::vector files; - files.push_back(std::move(*file1)); - files.push_back(std::move(*file2)); - - std::vector prereqFiles; - prereqFiles.push_back(std::move(*GetAppFile("prereqFileId", - "url", - 1 /*sizeInBytes*/, - {{HashType::Sha1, "sha1"}}, - {Architecture::Amd64}, - {"myPlatformApplicabilityForPackage"}, - "fileMoniker"))); - - std::vector prerequisites; - prerequisites.push_back( - std::move(*GetPrerequisiteContent(contentNameSpace, "prereqName", "prereqVersion", prereqFiles))); - - std::unique_ptr appContent = - GetAppContent(contentNameSpace, contentName, contentVersion, "updateId", prerequisites, files); - REQUIRE(appContent != nullptr); -} - -TEST("Testing AppContent equality operators") -{ - const std::string contentNameSpace{"myNameSpace"}; - const std::string contentName{"myName"}; - const std::string contentVersion{"myVersion"}; - const std::string updateId{"myUpdateId"}; - - std::unique_ptr file = GetAppFile("fileId", - "url", - 1 /*sizeInBytes*/, - {{HashType::Sha1, "sha1"}}, - {Architecture::Amd64}, - {"myPlatformApplicabilityForPackage"}, - "fileMoniker"); - - std::vector files; - files.push_back(std::move(*file)); - - std::vector prereqFiles; - prereqFiles.push_back(std::move(*GetAppFile("prereqFileId", - "url", - 1 /*sizeInBytes*/, - {{HashType::Sha1, "sha1"}}, - {Architecture::Amd64}, - {"myPlatformApplicabilityForPackage"}, - "fileMoniker"))); - - std::vector prerequisites; - prerequisites.push_back( - std::move(*GetPrerequisiteContent(contentNameSpace, "prereqName", "prereqVersion", prereqFiles))); - - const std::unique_ptr content = - GetAppContent(contentNameSpace, contentName, contentVersion, updateId, prerequisites, files); - - SECTION("Equal") - { - auto CompareAppContentEqual = [&content](const std::unique_ptr& sameContent) { - REQUIRE((*content == *sameContent)); - REQUIRE_FALSE((*content != *sameContent)); - }; - - CompareAppContentEqual( - GetAppContent(contentNameSpace, contentName, contentVersion, updateId, prerequisites, files)); - } - - SECTION("Not equal") - { - auto CompareAppContentNotEqual = [&content](const std::unique_ptr& otherContent) { - REQUIRE((*content != *otherContent)); - REQUIRE_FALSE((*content == *otherContent)); - }; - - CompareAppContentNotEqual(GetAppContent("", contentName, contentVersion, updateId, prerequisites, files)); - CompareAppContentNotEqual(GetAppContent(contentNameSpace, "", contentVersion, updateId, prerequisites, files)); - CompareAppContentNotEqual(GetAppContent(contentNameSpace, contentName, "", updateId, prerequisites, files)); - CompareAppContentNotEqual( - GetAppContent(contentNameSpace, contentName, contentVersion, "", prerequisites, files)); - CompareAppContentNotEqual(GetAppContent(contentNameSpace, contentName, contentVersion, updateId, {}, files)); - CompareAppContentNotEqual( - GetAppContent(contentNameSpace, contentName, contentVersion, updateId, prerequisites, {})); - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/AppFileTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/AppFileTests.cpp deleted file mode 100644 index 58ecb65dae..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/AppFileTests.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentUtil.h" -#include "sfsclient/AppFile.h" - -#include - -#define TEST(...) TEST_CASE("[AppFileTests] " __VA_ARGS__) -#define TEST_SCENARIO(...) TEST_CASE("[AppFileTests] Scenario: " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::contentutil; - -namespace -{ -std::unique_ptr GetAppFile(const std::string& fileId, - const std::string& url, - uint64_t sizeInBytes, - const std::unordered_map& hashes, - std::vector architectures, - std::vector platformApplicabilityForPackage, - std::string fileMoniker) -{ - std::unique_ptr file; - REQUIRE(AppFile::Make(fileId, - url, - sizeInBytes, - hashes, - architectures, - platformApplicabilityForPackage, - fileMoniker, - file) == Result::Success); - REQUIRE(file != nullptr); - return file; -} -} // namespace - -TEST("Testing AppFile::Make()") -{ - const std::string fileId{"myFileId"}; - const std::string url{"myUrl"}; - const uint64_t sizeInBytes{1234}; - const std::unordered_map hashes{{HashType::Sha1, "mySha1"}, {HashType::Sha256, "mySha256"}}; - const std::vector architectures{Architecture::Amd64}; - const std::vector platformApplicabilityForPackage{"myPlatformApplicabilityForPackage"}; - const std::string fileMoniker{"myFileMoniker"}; - - const std::unique_ptr file = - GetAppFile(fileId, url, sizeInBytes, hashes, architectures, platformApplicabilityForPackage, fileMoniker); - - CHECK(fileId == file->GetFileId()); - CHECK(url == file->GetUrl()); - CHECK(sizeInBytes == file->GetSizeInBytes()); - CHECK(hashes == file->GetHashes()); - - SECTION("Testing File equality operators") - { - SECTION("Equal") - { - auto CompareFileEqual = [&file](const std::unique_ptr& sameFile) { - REQUIRE((*file == *sameFile)); - REQUIRE_FALSE((*file != *sameFile)); - }; - - CompareFileEqual(GetAppFile(fileId, - url, - sizeInBytes, - hashes, - architectures, - platformApplicabilityForPackage, - fileMoniker)); - } - - SECTION("Not equal") - { - auto CompareFileNotEqual = [&file](const std::unique_ptr& otherFile) { - REQUIRE((*file != *otherFile)); - REQUIRE_FALSE((*file == *otherFile)); - }; - - CompareFileNotEqual( - GetAppFile("", url, sizeInBytes, hashes, architectures, platformApplicabilityForPackage, fileMoniker)); - CompareFileNotEqual(GetAppFile(fileId, - "", - sizeInBytes, - hashes, - architectures, - platformApplicabilityForPackage, - fileMoniker)); - CompareFileNotEqual( - GetAppFile(fileId, url, 0, hashes, architectures, platformApplicabilityForPackage, fileMoniker)); - CompareFileNotEqual( - GetAppFile(fileId, url, sizeInBytes, {}, architectures, platformApplicabilityForPackage, fileMoniker)); - CompareFileNotEqual( - GetAppFile(fileId, url, sizeInBytes, hashes, {}, platformApplicabilityForPackage, fileMoniker)); - CompareFileNotEqual(GetAppFile(fileId, url, sizeInBytes, hashes, architectures, {}, fileMoniker)); - CompareFileNotEqual(GetAppFile(fileId, url, sizeInBytes, hashes, {}, platformApplicabilityForPackage, {})); - CompareFileNotEqual(GetAppFile("", "", 0, {}, architectures, platformApplicabilityForPackage, fileMoniker)); - CompareFileNotEqual(GetAppFile("MYFILEID", - url, - sizeInBytes, - hashes, - architectures, - platformApplicabilityForPackage, - fileMoniker)); - CompareFileNotEqual(GetAppFile(fileId, - "MYURL", - sizeInBytes, - hashes, - architectures, - platformApplicabilityForPackage, - fileMoniker)); - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/ApplicabilityDetailsTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/ApplicabilityDetailsTests.cpp deleted file mode 100644 index 30458d5010..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/ApplicabilityDetailsTests.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ApplicabilityDetails.h" -#include "ContentUtil.h" - -#include - -#define TEST(...) TEST_CASE("[ApplicabilityDetailsTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details::contentutil; - -namespace -{ -std::unique_ptr GetDetails(const std::vector& architectures, - const std::vector& platformApplicabilityForPackage) -{ - std::unique_ptr details; - REQUIRE(ApplicabilityDetails::Make(architectures, platformApplicabilityForPackage, details) == Result::Success); - REQUIRE(details != nullptr); - return details; -}; -} // namespace - -TEST("Testing ApplicabilityDetails::Make()") -{ - const std::vector architectures{Architecture::x86, Architecture::Amd64}; - const std::vector platformApplicabilityForPackage{"Windows.Desktop", "Windows.Server"}; - const std::string fileMoniker{"myApp"}; - - const std::unique_ptr details = GetDetails(architectures, platformApplicabilityForPackage); - - CHECK(architectures == details->GetArchitectures()); - CHECK(platformApplicabilityForPackage == details->GetPlatformApplicabilityForPackage()); - - SECTION("Testing ApplicabilityDetails equality operators") - { - SECTION("Equal") - { - auto CompareDetailsEqual = [&details](const std::unique_ptr& sameDetails) { - REQUIRE((*details == *sameDetails)); - REQUIRE_FALSE((*details != *sameDetails)); - }; - - CompareDetailsEqual(GetDetails(architectures, platformApplicabilityForPackage)); - } - - SECTION("Not equal") - { - auto CompareDetailsNotEqual = [&details](const std::unique_ptr& otherDetails) { - REQUIRE((*details != *otherDetails)); - REQUIRE_FALSE((*details == *otherDetails)); - }; - - CompareDetailsNotEqual(GetDetails({}, platformApplicabilityForPackage)); - CompareDetailsNotEqual(GetDetails(architectures, {})); - CompareDetailsNotEqual(GetDetails({}, {})); - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/ContentIdTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/ContentIdTests.cpp deleted file mode 100644 index 3e76a9171b..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/ContentIdTests.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentUtil.h" -#include "sfsclient/ContentId.h" - -#include - -#define TEST(...) TEST_CASE("[ContentIdTests] " __VA_ARGS__) -#define TEST_SCENARIO(...) TEST_CASE("[ContentIdTests] Scenario: " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::contentutil; - -namespace -{ -std::unique_ptr GetContentId(const std::string& nameSpace, - const std::string& name, - const std::string& version) -{ - std::unique_ptr contentId; - REQUIRE(ContentId::Make(nameSpace, name, version, contentId) == Result::Success); - REQUIRE(contentId != nullptr); - return contentId; -}; -} // namespace - -TEST("Testing ContentId::Make()") -{ - const std::string nameSpace{"myNameSpace"}; - const std::string name{"myName"}; - const std::string version{"myVersion"}; - - const std::unique_ptr contentId = GetContentId(nameSpace, name, version); - - CHECK(nameSpace == contentId->GetNameSpace()); - CHECK(name == contentId->GetName()); - CHECK(version == contentId->GetVersion()); - - SECTION("Testing ContentId equality operators") - { - SECTION("Equal") - { - auto CompareContentIdEqual = [&contentId](const std::unique_ptr& sameContentId) { - REQUIRE((*contentId == *sameContentId)); - REQUIRE_FALSE((*contentId != *sameContentId)); - }; - - CompareContentIdEqual(GetContentId(nameSpace, name, version)); - } - - SECTION("Not equal") - { - auto CompareContentIdNotEqual = [&contentId](const std::unique_ptr& otherContentId) { - REQUIRE((*contentId != *otherContentId)); - REQUIRE_FALSE((*contentId == *otherContentId)); - }; - - CompareContentIdNotEqual(GetContentId("", name, version)); - CompareContentIdNotEqual(GetContentId(nameSpace, "", version)); - CompareContentIdNotEqual(GetContentId(nameSpace, name, "")); - CompareContentIdNotEqual(GetContentId("", "", "")); - CompareContentIdNotEqual(GetContentId("MYNAMESPACE", name, version)); - CompareContentIdNotEqual(GetContentId(nameSpace, "MYNAME", version)); - CompareContentIdNotEqual(GetContentId(nameSpace, name, "MYVERSION")); - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/ContentTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/ContentTests.cpp deleted file mode 100644 index 6bfd60494e..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/ContentTests.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentUtil.h" -#include "sfsclient/Content.h" - -#include - -#define TEST(...) TEST_CASE("[ContentTests] " __VA_ARGS__) -#define TEST_SCENARIO(...) TEST_CASE("[ContentTests] Scenario: " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::contentutil; - -namespace -{ -std::unique_ptr GetFile(const std::string& fileId, - const std::string& url, - uint64_t sizeInBytes, - const std::unordered_map& hashes) -{ - std::unique_ptr file; - REQUIRE(File::Make(fileId, url, sizeInBytes, hashes, file) == Result::Success); - REQUIRE(file != nullptr); - return file; -}; - -std::unique_ptr GetContent(const std::string& contentNameSpace, - const std::string& contentName, - const std::string& contentVersion, - const std::vector& files) -{ - std::unique_ptr content; - REQUIRE(Content::Make(contentNameSpace, contentName, contentVersion, files, content) == Result::Success); - REQUIRE(content != nullptr); - return content; -}; -} // namespace - -TEST_SCENARIO("Testing Content::Make()") -{ - GIVEN("Elements that make up a content") - { - const std::string contentNameSpace{"myNameSpace"}; - const std::string contentName{"myName"}; - const std::string contentVersion{"myVersion"}; - - std::unique_ptr file1 = GetFile("fileId1", "url1", 1 /*sizeInBytes*/, {{HashType::Sha1, "sha1"}}); - std::unique_ptr file2 = GetFile("fileId2", "url2", 1 /*sizeInBytes*/, {{HashType::Sha256, "sha256"}}); - - std::vector files; - files.push_back(std::move(*file1)); - files.push_back(std::move(*file2)); - - // Getting raw pointers to check they don't match after copy but match after move - std::vector filePointers; - for (size_t i = 0; i < files.size(); ++i) - { - filePointers.push_back(&files[i]); - } - - WHEN("A Content is created by copying the parameters") - { - std::unique_ptr copiedContent; - REQUIRE(Content::Make(contentNameSpace, contentName, contentVersion, files, copiedContent) == - Result::Success); - REQUIRE(copiedContent != nullptr); - - THEN("The content elements are copies") - { - CHECK(contentNameSpace == copiedContent->GetContentId().GetNameSpace()); - CHECK(contentName == copiedContent->GetContentId().GetName()); - CHECK(contentVersion == copiedContent->GetContentId().GetVersion()); - - // Files were cloned, so the pointers are different, but the contents should be similar - REQUIRE(files.size() == copiedContent->GetFiles().size()); - REQUIRE(filePointers.size() == copiedContent->GetFiles().size()); - for (size_t i = 0; i < files.size(); ++i) - { - // Checking ptrs - REQUIRE(filePointers[i] != &copiedContent->GetFiles()[i]); - REQUIRE(&files[i] != &copiedContent->GetFiles()[i]); - - // Checking contents - CHECK((files[i] == copiedContent->GetFiles()[i])); - } - } - - AND_THEN("Using the Make() that moves the file parameter really moves the parameter") - { - std::unique_ptr movedContent; - REQUIRE(Content::Make(contentNameSpace, contentName, contentVersion, std::move(files), movedContent) == - Result::Success); - REQUIRE(movedContent != nullptr); - - // Checking contents - CHECK((*copiedContent == *movedContent)); - - // Checking underlying pointers are the same since they were moved - REQUIRE(filePointers.size() == movedContent->GetFiles().size()); - REQUIRE(copiedContent->GetFiles().size() == movedContent->GetFiles().size()); - for (size_t i = 0; i < filePointers.size(); ++i) - { - REQUIRE(&copiedContent->GetFiles()[i] != &movedContent->GetFiles()[i]); - REQUIRE(filePointers[i] == &movedContent->GetFiles()[i]); - } - } - } - } -} - -TEST("Testing Content equality operators") -{ - const std::string contentNameSpace{"myNameSpace"}; - const std::string contentName{"myName"}; - const std::string contentVersion{"myVersion"}; - - std::unique_ptr file = GetFile("fileId", "url", 1 /*sizeInBytes*/, {{HashType::Sha1, "sha1"}}); - - std::unique_ptr clonedFile; - REQUIRE(File::Make(file->GetFileId(), file->GetUrl(), file->GetSizeInBytes(), file->GetHashes(), clonedFile) == - Result::Success); - - std::vector files; - files.push_back(std::move(*file)); - - std::vector clonedFiles; - clonedFiles.push_back(std::move(*clonedFile)); - - const std::unique_ptr content = GetContent(contentNameSpace, contentName, contentVersion, files); - - SECTION("Equal") - { - auto CompareContentEqual = [&content](const std::unique_ptr& sameContent) { - REQUIRE((*content == *sameContent)); - REQUIRE_FALSE((*content != *sameContent)); - }; - - CompareContentEqual(GetContent(contentNameSpace, contentName, contentVersion, files)); - CompareContentEqual(GetContent(contentNameSpace, contentName, contentVersion, clonedFiles)); - } - - SECTION("Not equal") - { - auto CompareContentNotEqual = [&content](const std::unique_ptr& otherContent) { - REQUIRE((*content != *otherContent)); - REQUIRE_FALSE((*content == *otherContent)); - }; - - CompareContentNotEqual(GetContent("", contentName, contentVersion, files)); - CompareContentNotEqual(GetContent(contentNameSpace, "", contentVersion, files)); - CompareContentNotEqual(GetContent(contentNameSpace, contentName, "", files)); - CompareContentNotEqual(GetContent(contentNameSpace, contentName, contentVersion, {})); - CompareContentNotEqual(GetContent("", "", "", {})); - CompareContentNotEqual(GetContent("MYNAMESPACE", contentName, contentVersion, files)); - CompareContentNotEqual(GetContent(contentNameSpace, "MYNAME", contentVersion, files)); - CompareContentNotEqual(GetContent(contentNameSpace, contentName, "MYVERSION", files)); - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/FileTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/FileTests.cpp deleted file mode 100644 index 73263b7722..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/FileTests.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ContentUtil.h" -#include "sfsclient/File.h" - -#include - -#define TEST(...) TEST_CASE("[FileTests] " __VA_ARGS__) -#define TEST_SCENARIO(...) TEST_CASE("[FileTests] Scenario: " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::details::contentutil; - -namespace -{ -std::unique_ptr GetFile(const std::string& fileId, - const std::string& url, - uint64_t sizeInBytes, - const std::unordered_map& hashes) -{ - std::unique_ptr file; - REQUIRE(File::Make(fileId, url, sizeInBytes, hashes, file) == Result::Success); - REQUIRE(file != nullptr); - return file; -}; -} // namespace - -TEST("Testing File::Make()") -{ - const std::string fileId{"myFileId"}; - const std::string url{"myUrl"}; - const uint64_t sizeInBytes{1234}; - const std::unordered_map hashes{{HashType::Sha1, "mySha1"}, {HashType::Sha256, "mySha256"}}; - - const std::unique_ptr file = GetFile(fileId, url, sizeInBytes, hashes); - - CHECK(fileId == file->GetFileId()); - CHECK(url == file->GetUrl()); - CHECK(sizeInBytes == file->GetSizeInBytes()); - CHECK(hashes == file->GetHashes()); - - SECTION("Testing File equality operators") - { - SECTION("Equal") - { - auto CompareFileEqual = [&file](const std::unique_ptr& sameFile) { - REQUIRE((*file == *sameFile)); - REQUIRE_FALSE((*file != *sameFile)); - }; - - CompareFileEqual(GetFile(fileId, url, sizeInBytes, hashes)); - } - - SECTION("Not equal") - { - auto CompareFileNotEqual = [&file](const std::unique_ptr& otherFile) { - REQUIRE((*file != *otherFile)); - REQUIRE_FALSE((*file == *otherFile)); - }; - - CompareFileNotEqual(GetFile("", url, sizeInBytes, hashes)); - CompareFileNotEqual(GetFile(fileId, "", sizeInBytes, hashes)); - CompareFileNotEqual(GetFile(fileId, url, 0, hashes)); - CompareFileNotEqual(GetFile(fileId, url, sizeInBytes, {})); - CompareFileNotEqual(GetFile("", "", 0, {})); - CompareFileNotEqual(GetFile("MYFILEID", url, sizeInBytes, hashes)); - CompareFileNotEqual(GetFile(fileId, "MYURL", sizeInBytes, hashes)); - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/ResultTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/ResultTests.cpp deleted file mode 100644 index aece41bda7..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/ResultTests.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "sfsclient/Result.h" - -#include - -#define TEST(...) TEST_CASE("[ResultTests] " __VA_ARGS__) - -using namespace SFS; - -TEST("Testing Result() class methods") -{ - SECTION("Default constructor") - { - Result resultSuccess(Result::Code::Success); - - REQUIRE(resultSuccess.GetCode() == Result::Code::Success); - REQUIRE(resultSuccess.GetMsg().empty()); - REQUIRE(resultSuccess.IsSuccess()); - REQUIRE_FALSE(resultSuccess.IsFailure()); - - INFO("Comparison operators"); - REQUIRE(resultSuccess == Result::Code::Success); - REQUIRE(resultSuccess == Result::Success); - REQUIRE(resultSuccess != Result::Code::NotSet); - REQUIRE(resultSuccess != Result::NotSet); - REQUIRE_FALSE(resultSuccess == Result::NotSet); - - INFO("bool operator"); - REQUIRE(resultSuccess); - } - - SECTION("Constructor with message") - { - Result resultUnexpected(Result::Code::Unexpected, "message"); - REQUIRE(resultUnexpected.GetCode() == Result::Code::Unexpected); - REQUIRE(resultUnexpected.GetMsg() == "message"); - REQUIRE_FALSE(resultUnexpected.IsSuccess()); - REQUIRE(resultUnexpected.IsFailure()); - - INFO("Comparison operators on constructor with message"); - REQUIRE(resultUnexpected == Result::Code::Unexpected); - REQUIRE(resultUnexpected != Result::Code::NotSet); - - INFO("bool operator"); - REQUIRE_FALSE(resultUnexpected); - } -} - -TEST("Testing ToString(Result)") -{ - REQUIRE(SFS::ToString(Result::Code::Success) == "Success"); - REQUIRE(SFS::ToString(Result::Code::NotImpl) == "NotImpl"); - REQUIRE(SFS::ToString(Result::Code::NotSet) == "NotSet"); - REQUIRE(SFS::ToString(Result::Code::OutOfMemory) == "OutOfMemory"); - REQUIRE(SFS::ToString(Result::Code::Unexpected) == "Unexpected"); -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp deleted file mode 100644 index 33cc0d5cc3..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "sfsclient/ApplicabilityDetails.h" -#include "sfsclient/SFSClient.h" - -#include - -#include - -#define TEST(...) TEST_CASE("[SFSClientTests] " __VA_ARGS__) -#define TEST_SCENARIO(...) TEST_CASE("[SFSClientTests] Scenario: " __VA_ARGS__) - -using namespace SFS; - -namespace -{ -std::unique_ptr GetSFSClient(std::optional instanceId = std::nullopt) -{ - std::unique_ptr sfsClient; - ClientConfig options; - options.accountId = "testAccountId"; - if (instanceId) - { - options.instanceId = *instanceId; - } - REQUIRE(SFSClient::Make(options, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - return sfsClient; -} - -void TestLoggingCallback(const LogData&) -{ -} - -struct TestLoggingCallbackStruct -{ - static void TestLoggingCallback(const LogData&) - { - } -}; -} // namespace - -static void StaticTestLoggingCallback(const LogData&) -{ -} - -TEST("Testing SFSClient::Make()") -{ -#ifdef __GNUG__ -// For GCC, explicitly turning off "missing-field-initializers" warning as this block is testing the scenario -// in which a user calls explicitly onto the API with field initializers for ClientConfig -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - - const std::string accountId{"testAccountId"}; - const std::string instanceId{"testInstanceId"}; - const std::string nameSpace{"testNameSpace"}; - - std::unique_ptr sfsClient; - - SECTION("Make({accountId}, out)") - { - REQUIRE(SFSClient::Make({accountId}, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - - ClientConfig config{accountId}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Make(accountId, instanceId, out)") - { - REQUIRE(SFSClient::Make({accountId, instanceId}, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - - ClientConfig config{accountId, instanceId}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Make(accountId, instanceId, namespace, out)") - { - REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace}, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - - SECTION("Call make when the pointer is reset") - { - sfsClient.reset(); - REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace}, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Call make if the pointer is not reset, as Make() resets it") - { - REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace}, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("We can also use a separate ClientConfig object") - { - ClientConfig config{accountId, instanceId, nameSpace}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("We can also move a separate ClientConfig object") - { - ClientConfig config{accountId, instanceId, nameSpace}; - REQUIRE(SFSClient::Make(std::move(config), sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - } - - SECTION("Make(accountId, std::nullopt, nameSpace, out)") - { - REQUIRE(SFSClient::Make({accountId, std::nullopt, nameSpace}, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - - ClientConfig config; - config.accountId = accountId; - config.nameSpace = nameSpace; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Make(accountId, instanceId, namespace, logCallbackFn, out) works") - { - SECTION("Using a lambda with {} initialization") - { - REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace, [](const LogData&) {}}, sfsClient) == - Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Using a lambda with a ClientConfig object") - { - ClientConfig config{accountId, instanceId, nameSpace, [](const LogData&) {}}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Using a nullptr with a ClientConfig object") - { - ClientConfig config{accountId, instanceId, nameSpace, nullptr}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Using a valid empty-namespace function within a ClientConfig object") - { - ClientConfig config{accountId, instanceId, nameSpace, TestLoggingCallback}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Using a valid static function within a ClientConfig object") - { - ClientConfig config{accountId, instanceId, nameSpace, StaticTestLoggingCallback}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Using a valid static member method within a ClientConfig object") - { - ClientConfig config{accountId, instanceId, nameSpace, &TestLoggingCallbackStruct::TestLoggingCallback}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - - SECTION("Can also move a lambda") - { - ClientConfig config{accountId, instanceId, nameSpace, [](const LogData&) {}}; - REQUIRE(SFSClient::Make(std::move(config), sfsClient) == Result::Success); - REQUIRE(sfsClient != nullptr); - } - } - - SECTION("AccountId cannot be empty") - { - REQUIRE(SFSClient::Make({}, sfsClient) == Result::InvalidArg); - REQUIRE(sfsClient == nullptr); - - ClientConfig config; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg); - REQUIRE(sfsClient == nullptr); - - config = {}; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg); - REQUIRE(sfsClient == nullptr); - - config.accountId = std::string(); - REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg); - REQUIRE(sfsClient == nullptr); - - config.instanceId = instanceId; - REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg); - REQUIRE(sfsClient == nullptr); - } - -#ifdef __GNUG__ -// For "-Wmissing-field-initializers" -#pragma GCC diagnostic pop -#endif -} - -namespace -{ -void TestProductInRequestParams(const std::function& apiCall, - const std::function& checkContents) -{ - RequestParams params; - SECTION("Product must not be empty") - { - const std::string expectedErrorMsg = "product must not be empty"; - - params.productRequests = {{"", {}}}; - auto result = apiCall(params); - REQUIRE(result.GetCode() == Result::InvalidArg); - REQUIRE(result.GetMsg() == expectedErrorMsg); - checkContents(); - - const TargetingAttributes attributes{{"attr1", "value"}}; - params.productRequests = {{"", attributes}}; - result = apiCall(params); - REQUIRE(result.GetCode() == Result::InvalidArg); - REQUIRE(result.GetMsg() == expectedErrorMsg); - checkContents(); - } - - SECTION("Does not allow an empty request") - { - auto result = apiCall(params); - REQUIRE(result.GetCode() == Result::InvalidArg); - REQUIRE(result.GetMsg() == "productRequests cannot be empty"); - checkContents(); - } - - SECTION("Accepting multiple products is not implemented yet") - { - params.productRequests = {{"p1", {}}, {"p2", {}}}; - auto result = apiCall(params); - REQUIRE(result.GetCode() == Result::NotImpl); - REQUIRE(result.GetMsg() == "There cannot be more than 1 productRequest at the moment"); - checkContents(); - } - - SECTION("Fails if base cv is not correct") - { - params.productRequests = {{"p1", {}}}; - params.baseCV = ""; - auto result = apiCall(params); - REQUIRE(result.GetCode() == Result::InvalidArg); - REQUIRE(result.GetMsg() == "cv must not be empty"); - checkContents(); - - params.baseCV = "cv"; - result = apiCall(params); - REQUIRE(result.GetCode() == Result::InvalidArg); - REQUIRE(result.GetMsg().find("baseCV is not a valid correlation vector:") == 0); - checkContents(); - } - - SECTION("Fails if proxy is setup incorrectly") - { - params.productRequests = {{"p1", {}}}; - params.proxy = "bad://"; - auto result = apiCall(params); - REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError); - REQUIRE(result.GetMsg() == "Unsupported proxy syntax in 'bad://': No host part in the URL"); - checkContents(); - - params.proxy = "bad://bad.com"; - result = apiCall(params); - REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError); - REQUIRE(result.GetMsg() == "Unsupported proxy scheme for 'bad://bad.com'"); - checkContents(); - - params.proxy = ":"; - result = apiCall(params); - REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError); - REQUIRE(result.GetMsg() == - "Unsupported proxy syntax in ':': Port number was not a decimal number between 0 and 65535"); - checkContents(); - - params.proxy = "http://bad:bad"; - result = apiCall(params); - REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError); - REQUIRE( - result.GetMsg() == - "Unsupported proxy syntax in 'http://bad:bad': Port number was not a decimal number between 0 and 65535"); - checkContents(); - - params.proxy = "bad:bad"; - result = apiCall(params); - REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError); - REQUIRE(result.GetMsg() == - "Unsupported proxy syntax in 'bad:bad': Port number was not a decimal number between 0 and 65535"); - checkContents(); - } -} -} // namespace - -TEST("Testing SFSClient::GetLatestDownloadInfo()") -{ - auto sfsClient = GetSFSClient(); - std::vector contents; - - TestProductInRequestParams( - [&](const RequestParams& params) { return sfsClient->GetLatestDownloadInfo(params, contents); }, - [&contents] { REQUIRE(contents.empty()); }); -} - -TEST("Testing SFSClient::GetAppLatestDownloadInfo()") -{ - SECTION("With storeapps instance") - { - auto sfsClient = GetSFSClient("storeapps"); - std::vector contents; - - TestProductInRequestParams( - [&](const RequestParams& params) { return sfsClient->GetLatestAppDownloadInfo(params, contents); }, - [&contents] { REQUIRE(contents.empty()); }); - } - - SECTION("Fails if not storeapps instanceId") - { - auto sfsClient = GetSFSClient("testInstanceId"); - std::vector contents; - RequestParams params; - params.productRequests = {{"a", {}}}; - auto result = sfsClient->GetLatestAppDownloadInfo(params, contents); - REQUIRE(result.GetCode() == Result::Unexpected); - REQUIRE(result.GetMsg() == "At this moment only the \"storeapps\" instanceId can send app requests"); - REQUIRE(contents.empty()); - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionManagerTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionManagerTests.cpp deleted file mode 100644 index ed739e1d57..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionManagerTests.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ReportingHandler.h" -#include "connection/Connection.h" -#include "connection/CurlConnection.h" -#include "connection/CurlConnectionManager.h" - -#include -#include - -using namespace SFS; -using namespace SFS::details; - -#define TEST(...) TEST_CASE("[CurlConnectionManagerTests] " __VA_ARGS__) - -TEST("Testing expected values in curl_version_info_data") -{ - curl_version_info_data* ver = curl_version_info(CURLVERSION_NOW); - REQUIRE(ver != nullptr); - CHECK(ver->features & CURL_VERSION_SSL); - - // Checking thread safety is on - CHECK(ver->features & CURL_VERSION_THREADSAFE); - - // For thread safety we need the DNS resolutions to be asynchronous (which happens because of c-ares) - CHECK(ver->features & CURL_VERSION_ASYNCHDNS); -} - -TEST("Testing CurlConnectionManager()") -{ - ReportingHandler handler; - CurlConnectionManager curlConnectionManager(handler); - - // Check that the CurlConnectionManager generates a CurlConnection object - std::unique_ptr Connection = curlConnectionManager.MakeConnection({}); - REQUIRE(Connection != nullptr); - REQUIRE(dynamic_cast(Connection.get()) != nullptr); - - // Having many CurlConnectionManager objects should not cause any issues, curl is smart enough to - // handle multiple initialization and cleanup calls - CurlConnectionManager curlConnectionManager2(handler); - auto Connection2 = curlConnectionManager2.MakeConnection({}); - - CurlConnectionManager curlConnectionManager3(handler); - auto Connection3 = curlConnectionManager3.MakeConnection({}); - auto Connection4 = curlConnectionManager3.MakeConnection({}); -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionTests.cpp deleted file mode 100644 index 2915371ff1..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/CurlConnectionTests.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../util/SFSExceptionMatcher.h" -#include "../../util/TestHelper.h" -#include "ReportingHandler.h" -#include "Result.h" -#include "connection/CurlConnection.h" -#include "connection/mock/MockConnection.h" - -#include -#include -#include - -#define TEST(...) TEST_CASE("[CurlConnectionTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; -using json = nlohmann::json; - -namespace -{ -class MockCurlConnection : public CurlConnection -{ - public: - MockCurlConnection(const ReportingHandler& handler, Result::Code& responseCode, std::string& response) - : CurlConnection({}, handler) - , m_responseCode(responseCode) - , m_response(response) - { - } - - protected: - std::string CurlPerform(const std::string&, CurlHeaderList&) override - { - if (m_responseCode == Result::Success) - { - return m_response; - } - throw SFSException(m_responseCode); - } - - private: - Result::Code& m_responseCode; - std::string& m_response; -}; -} // namespace - -TEST("Testing CurlConnection()") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - Result::Code responseCode = Result::HttpNotFound; - std::string response = "expected"; - std::unique_ptr connection = std::make_unique(handler, responseCode, response); - - SECTION("Testing CurlConnection::Get()") - { - std::string out; - REQUIRE_THROWS_CODE(out = connection->Get("url"), HttpNotFound); - REQUIRE(out.empty()); - - responseCode = Result::Success; - REQUIRE_NOTHROW(out = connection->Get("url")); - REQUIRE(out == response); - } - - SECTION("Testing CurlConnection::Post()") - { - std::string out; - REQUIRE_THROWS_CODE(out = connection->Post("url"), HttpNotFound); - REQUIRE(out.empty()); - - const json body = {{{"dummy", {}}}}; - REQUIRE_THROWS_CODE(out = connection->Post("url", body.dump()), HttpNotFound); - REQUIRE(out.empty()); - - responseCode = Result::Success; - REQUIRE_NOTHROW(out = connection->Get("url")); - REQUIRE(out == response); - - response.clear(); - REQUIRE_NOTHROW(out = connection->Post("url", body.dump())); - REQUIRE(out == response); - } -} - -TEST("Testing CurlConnection constructor passing a cv") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - ConnectionConfig config; - config.baseCV = ""; - - REQUIRE_THROWS_CODE_MSG(std::make_unique(config, handler), InvalidArg, "cv must not be empty"); - - config.baseCV = "cv"; - REQUIRE_THROWS_CODE_MSG_MATCHES(std::make_unique(config, handler), - InvalidArg, - Catch::Matchers::ContainsSubstring("baseCV is not a valid correlation vector:")); - - config.baseCV = "aaaaaaaaaaaaaaaa.1"; - REQUIRE_NOTHROW(std::make_unique(config, handler)); -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/EnvTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/EnvTests.cpp deleted file mode 100644 index 1d136adffe..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/EnvTests.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Env.h" - -#include - -#define TEST(...) TEST_CASE("[EnvTests] " __VA_ARGS__) - -using namespace SFS::details::env; - -TEST("Testing GetEnv()") -{ - SECTION("Testing GetEnv() on an existing environment variable") - { - // Get the value of an existing environment variable per platform -#ifdef _WIN32 - const std::string varName = "COMPUTERNAME"; -#else - const std::string varName = "LANG"; -#endif - auto env = GetEnv(varName); - REQUIRE(env.has_value()); - REQUIRE(env.value().size() > 0); - } - - SECTION("Testing GetEnv() on a non-existing variable") - { - auto env = GetEnv("DUMMYVARIABLESHOULDNOTEXIST"); - REQUIRE(!env.has_value()); - } -} - -TEST("Testing SetEnv()") -{ - SECTION("Testing SetEnv() on a non-existing variable") - { - auto env = GetEnv("DUMMYVARIABLE"); - REQUIRE(!env.has_value()); - - REQUIRE(SetEnv("DUMMYVARIABLE", "dummyValue")); - env = GetEnv("DUMMYVARIABLE"); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue"); - - SECTION("Testing SetEnv() on an existing variable overwrites it") - { - REQUIRE(SetEnv("DUMMYVARIABLE", "dummyValue2")); - - env = GetEnv("DUMMYVARIABLE"); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue2"); - } - - INFO("Unsetting the environment variable"); - REQUIRE(UnsetEnv("DUMMYVARIABLE")); - env = GetEnv("DUMMYVARIABLE"); - REQUIRE(!env.has_value()); - } - - SECTION("Testing SetEnv() with empty strings fails") - { - REQUIRE_FALSE(SetEnv("DUMMYVARIABLE", "")); - REQUIRE_FALSE(SetEnv("DUMMYVARIABLE", std::string())); - REQUIRE_FALSE(SetEnv("", "dummy")); - REQUIRE_FALSE(SetEnv(std::string(), "dummy")); - REQUIRE_FALSE(SetEnv(std::string(), std::string())); - } -} - -TEST("Testing UnsetEnv()") -{ - SECTION("Testing UnsetEnv() on a non-existing variable still succeeds") - { - auto env = GetEnv("DUMMYVARIABLE"); - REQUIRE(!env.has_value()); - - REQUIRE(UnsetEnv("DUMMYVARIABLE")); - } -} - -TEST("Testing ScopedEnv") -{ - SECTION("Testing ScopedEnv on a non-existing variable") - { - auto env = GetEnv("DUMMYVARIABLE"); - REQUIRE(!env.has_value()); - - { - ScopedEnv scopedEnv("DUMMYVARIABLE", "dummyValue"); - - INFO("Variable exists within scope"); - env = GetEnv("DUMMYVARIABLE"); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue"); - - SECTION("Testing ScopedEnv on an existing variable overwrites it") - { - { - ScopedEnv scopedEnv2("DUMMYVARIABLE", "dummyValue2"); - - INFO("Variable has value overwritten within scope"); - env = GetEnv("DUMMYVARIABLE"); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue2"); - } - - INFO("Variable goes back to previous value"); - env = GetEnv("DUMMYVARIABLE"); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue"); - } - } - - INFO("Variable should be unset after the scope ends"); - env = GetEnv("DUMMYVARIABLE"); - REQUIRE(!env.has_value()); - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/ErrorHandlingTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/ErrorHandlingTests.cpp deleted file mode 100644 index 6b8b9ef88f..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/ErrorHandlingTests.cpp +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ErrorHandling.h" -#include "ReportingHandler.h" -#include "Result.h" -#include "SFSException.h" - -#include - -#define TEST(...) TEST_CASE("[ErrorHandlingTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; - -namespace -{ -Result TestSFS_Catch_Return_bad_alloc() -try -{ - throw std::bad_alloc(); -} -SFS_CATCH_RETURN(); - -class MyException : public std::exception -{ -}; - -Result TestSFS_Catch_Return_std_exception() -try -{ - throw MyException(); -} -SFS_CATCH_RETURN(); - -Result TestSFS_Catch_Return_unknown() -try -{ - throw std::error_code(); -} -SFS_CATCH_RETURN(); -} // namespace - -TEST("Testing ErrorHandling's SFS_CATCH_RETURN()") -{ - REQUIRE(TestSFS_Catch_Return_bad_alloc() == Result::OutOfMemory); - REQUIRE(TestSFS_Catch_Return_std_exception() == Result::Unexpected); - REQUIRE(TestSFS_Catch_Return_unknown() == Result::Unexpected); -} - -namespace -{ -void TestSFS_Catch_Log_Rethrow_bad_alloc(const ReportingHandler& handler) -try -{ - throw std::bad_alloc(); -} -SFS_CATCH_LOG_RETHROW(handler); - -void TestSFS_Catch_Log_Rethrow_SFSException(const ReportingHandler& handler) -try -{ - throw SFS::details::SFSException(Result::Unexpected); -} -SFS_CATCH_LOG_RETHROW(handler); -} // namespace - -TEST("Testing ErrorHandling's SFS_CATCH_LOG_RETHROW()") -{ - ReportingHandler handler; - bool called = false; - handler.SetLoggingCallback([&](const auto&) { called = true; }); - - SECTION("Test that SFS_CATCH_LOG_RETHROW does not rethrow if not SFSException") - { - REQUIRE_THROWS_AS(TestSFS_Catch_Log_Rethrow_bad_alloc(handler), std::bad_alloc); - REQUIRE(!called); - } - - SECTION("Test that SFS_CATCH_LOG_RETHROW rethrows and logs if SFSException") - { - REQUIRE_THROWS_AS(TestSFS_Catch_Log_Rethrow_SFSException(handler), SFSException); - REQUIRE(called); - } -} - -namespace -{ -Result TestSFS_ReturnIfFailed(const Result& result, const Result& ifNotFailed = Result::Success) -{ - RETURN_IF_FAILED(result); - return ifNotFailed; -} -} // namespace - -TEST("Testing ErrorHandling's RETURN_IF_FAILED()") -{ - SECTION("Test that RETURN_IF_FAILED returns the result if it is a failure") - { - REQUIRE(TestSFS_ReturnIfFailed(Result::Code::Unexpected) == Result::Code::Unexpected); - } - - SECTION("Test that RETURN_IF_FAILED does not return if the result is a success") - { - REQUIRE(TestSFS_ReturnIfFailed(Result::Code::Success) == Result::Code::Success); - REQUIRE(TestSFS_ReturnIfFailed(Result::Code::Success, Result::Code::Unexpected) == Result::Code::Unexpected); - } -} - -namespace -{ -Result TestSFS_ReturnIfFailedLog(const ReportingHandler& handler, - const Result& result, - const Result& ifNotFailed = Result::Success) -{ - RETURN_IF_FAILED_LOG(result, handler); - return ifNotFailed; -} -} // namespace - -TEST("Testing ErrorHandling's RETURN_IF_FAILED_LOG()") -{ - ReportingHandler handler; - bool called = false; - handler.SetLoggingCallback([&](const auto&) { called = true; }); - - SECTION("Test that RETURN_IF_FAILED_LOG returns and logs if the result is a failure") - { - REQUIRE(TestSFS_ReturnIfFailedLog(handler, Result::Code::Unexpected) == Result::Code::Unexpected); - REQUIRE(called); - } - - SECTION("Test that RETURN_IF_FAILED_LOG does not return and log if the result is a success") - { - REQUIRE(TestSFS_ReturnIfFailedLog(handler, Result::Code::Success) == Result::Code::Success); - REQUIRE(!called); - } -} - -TEST("Testing ErrorHandling's LOG_IF_FAILED()") -{ - ReportingHandler handler; - bool called = false; - handler.SetLoggingCallback([&](const auto&) { called = true; }); - - SECTION("Test that LOG_IF_FAILED logs if the result is a failure") - { - LOG_IF_FAILED(Result(Result::Unexpected), handler); - REQUIRE(called); - } - - SECTION("Test that LOG_IF_FAILED does not log if the result is a success") - { - LOG_IF_FAILED(Result(Result::Success), handler); - REQUIRE(!called); - } -} - -namespace -{ -void TestSFS_ThrowLog(const ReportingHandler& handler, const Result& result) -{ - THROW_LOG(result, handler); -} -} // namespace - -TEST("Testing ErrorHandling's THROW_LOG()") -{ - INFO("Test that ErrorHandling's THROW_LOG throws and logs the result"); - - ReportingHandler handler; - bool called = false; - handler.SetLoggingCallback([&](const auto&) { called = true; }); - - REQUIRE_THROWS_AS(TestSFS_ThrowLog(handler, Result::Code::Unexpected), SFSException); - REQUIRE(called); -} - -namespace -{ -void TestSFS_ThrowIfFailedLog(const ReportingHandler& handler, const Result& result) -{ - THROW_IF_FAILED_LOG(result, handler); -} -} // namespace - -TEST("Testing ErrorHandling's THROW_IF_FAILED_LOG()") -{ - ReportingHandler handler; - bool called = false; - handler.SetLoggingCallback([&](const auto&) { called = true; }); - - SECTION("Test that ErrorHandling's THROW_IF_FAILED_LOG throws and logs the result if it is a failure") - { - REQUIRE_THROWS_AS(TestSFS_ThrowIfFailedLog(handler, Result::Code::Unexpected), SFSException); - REQUIRE(called); - } - - SECTION("Test that ErrorHandling's THROW_IF_FAILED_LOG does not throw and log the result if it is not a failure") - { - REQUIRE_NOTHROW(TestSFS_ThrowIfFailedLog(handler, Result::Code::Success)); - REQUIRE(!called); - } -} - -TEST("Testing ErrorHandling's THROW_CODE_IF()") -{ - SECTION("Test that THROW_CODE_IF throws if the condition is true") - { - REQUIRE_THROWS_AS([]() { THROW_CODE_IF(Unexpected, true); }(), SFSException); - } - - SECTION("Test that THROW_CODE_IF does not throw if the condition is false") - { - REQUIRE_NOTHROW([]() { THROW_CODE_IF(Unexpected, false); }()); - } -} - -TEST("Testing ErrorHandling's THROW_CODE_IF_LOG()") -{ - ReportingHandler handler; - bool called = false; - handler.SetLoggingCallback([&](const auto&) { called = true; }); - - SECTION("Test that THROW_CODE_IF_LOG throws and logs the result if the condition is true") - { - REQUIRE_THROWS_AS([&handler]() { THROW_CODE_IF_LOG(Unexpected, true, handler); }(), SFSException); - REQUIRE(called); - } - - SECTION("Test that THROW_CODE_IF_LOG does not throw and log if the condition is false") - { - REQUIRE_NOTHROW([&handler]() { THROW_CODE_IF_LOG(Unexpected, false, handler); }()); - REQUIRE(!called); - } -} - -TEST("Testing ErrorHandling's THROW_CODE_IF_NOT_LOG()") -{ - ReportingHandler handler; - bool called = false; - handler.SetLoggingCallback([&](const auto&) { called = true; }); - - SECTION("Test that THROW_CODE_IF_NOT_LOG throws and logs the result if the condition is false") - { - REQUIRE_THROWS_AS([&handler]() { THROW_CODE_IF_NOT_LOG(Unexpected, false, handler); }(), SFSException); - REQUIRE(called); - } - - SECTION("Test that THROW_CODE_IF_NOT_LOG does not throw and log if the condition is true") - { - REQUIRE_NOTHROW([&handler]() { THROW_CODE_IF_NOT_LOG(Unexpected, true, handler); }()); - REQUIRE(!called); - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/ReportingHandlerTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/ReportingHandlerTests.cpp deleted file mode 100644 index 72c7b17893..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/ReportingHandlerTests.cpp +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "ReportingHandler.h" - -#include - -#include -#include -#include -#include - -#define TEST(...) TEST_CASE("[ReportingHandlerTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; - -TEST("Testing SetLoggingCallback()") -{ - ReportingHandler handler; - - bool called = false; - auto handling = [&](const LogData&) { called = true; }; - - handler.SetLoggingCallback(handling); - REQUIRE_FALSE(called); - - LOG_INFO(handler, "Test"); - - called = false; - handler.SetLoggingCallback(nullptr); - - LOG_INFO(handler, "Test"); - REQUIRE_FALSE(called); -} - -TEST("Testing Severities") -{ - ReportingHandler handler; - - std::optional severity; - auto handling = [&](const LogData& data) { severity = data.severity; }; - - handler.SetLoggingCallback(handling); - - REQUIRE(!severity.has_value()); - - LOG_INFO(handler, "Test"); - REQUIRE(severity.has_value()); - REQUIRE(*severity == LogSeverity::Info); - severity.reset(); - - LOG_WARNING(handler, "Test"); - REQUIRE(severity.has_value()); - REQUIRE(*severity == LogSeverity::Warning); - severity.reset(); - - LOG_ERROR(handler, "Test"); - REQUIRE(severity.has_value()); - REQUIRE(*severity == LogSeverity::Error); - severity.reset(); - - LOG_VERBOSE(handler, "Test"); - REQUIRE(severity.has_value()); - REQUIRE(*severity == LogSeverity::Verbose); - - handler.SetLoggingCallback(nullptr); -} - -TEST("Testing file/line/function") -{ - ReportingHandler handler; - - std::string file; - int line = 0; - std::string function; - auto handling = [&](const LogData& data) { - file = std::string(data.file); - line = data.line; - function = std::string(data.function); - }; - - handler.SetLoggingCallback(handling); - - LOG_INFO(handler, "Test"); - CHECK(file.find("ReportingHandlerTests.cpp") != std::string::npos); - CHECK(line == (__LINE__ - 2)); - CHECK(function == "CATCH2_INTERNAL_TEST_4"); - - LOG_WARNING(handler, "Test"); - CHECK(file.find("ReportingHandlerTests.cpp") != std::string::npos); - CHECK(line == (__LINE__ - 2)); - CHECK(function == "CATCH2_INTERNAL_TEST_4"); -} - -TEST("Testing LogFormatting") -{ - ReportingHandler handler; - - std::string message; - auto handling = [&](const LogData& data) { message = data.message; }; - - handler.SetLoggingCallback(handling); - - REQUIRE(message.empty()); - - LOG_INFO(handler, "Test %s", "Test"); - REQUIRE(message == "Test Test"); - - LOG_WARNING(handler, "Test %s %s", "Test1", "Test2"); - REQUIRE(message == "Test Test1 Test2"); - - LOG_ERROR(handler, "Test %s %s %s", "Test1", "Test2", "Test3"); - REQUIRE(message == "Test Test1 Test2 Test3"); - - LOG_INFO(handler, "Test %d %d", 1, true); - REQUIRE(message == "Test 1 1"); - - LOG_INFO(handler, "Test %d %s", 2, false ? "true" : "false"); - REQUIRE(message == "Test 2 false"); - - handler.SetLoggingCallback(nullptr); -} - -TEST("Testing setting another logging callback waits for an existing call to finish") -{ - ReportingHandler handler; - - // Set a callback that will be blocked by a mutex - std::mutex mutex; - bool called = false; - std::chrono::time_point time1; - bool startedCall = false; - auto handling = [&](const LogData& logData) { - startedCall = true; - std::lock_guard guard(mutex); - called = true; - time1 = logData.time; - }; - - handler.SetLoggingCallback(handling); - - // Make sure the callback is blocked - std::unique_lock lock(mutex); - - // Spawn a thread that will be blocked by the callback - std::thread t([&]() { LOG_INFO(handler, "Test"); }); - - LoggingCallbackFn anotherHandling; - SECTION("Setting another callback") - { - anotherHandling = [&](const LogData&) {}; - } - SECTION("Setting a nullptr callback") - { - anotherHandling = nullptr; - } - - // Spawn a second thread that tries to set another callback - std::chrono::time_point time2; - std::thread t2([&]() { - // Make sure the callback has started and is now blocked - while (!startedCall) - { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - // Now setting another callback should be blocked until we unlock the mutex - handler.SetLoggingCallback(std::move(anotherHandling)); - INFO("The first callback should have been called at this point"); - REQUIRE(called); - time2 = std::chrono::system_clock::now(); - }); - - // Unlocking the mutex will allow the threads to continue - lock.unlock(); - t.join(); - t2.join(); - - INFO("The first callback should have been called before the second callback was set"); - REQUIRE(time1 < time2); -} - -TEST("Testing ToString(LogSeverity)") -{ - REQUIRE(SFS::ToString(LogSeverity::Info) == "Info"); - REQUIRE(SFS::ToString(LogSeverity::Warning) == "Warning"); - REQUIRE(SFS::ToString(LogSeverity::Error) == "Error"); - REQUIRE(SFS::ToString(LogSeverity::Verbose) == "Verbose"); -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/SFSClientImplTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/SFSClientImplTests.cpp deleted file mode 100644 index 8e2ed24f62..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/SFSClientImplTests.cpp +++ /dev/null @@ -1,410 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../util/SFSExceptionMatcher.h" -#include "../../util/TestHelper.h" -#include "SFSClientImpl.h" -#include "TestOverride.h" -#include "connection/Connection.h" -#include "connection/ConnectionManager.h" -#include "connection/CurlConnection.h" -#include "connection/CurlConnectionManager.h" -#include "connection/mock/MockConnectionManager.h" - -#include -#include - -#define TEST(...) TEST_CASE("[SFSClientImplTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; -using json = nlohmann::json; - -namespace -{ -class MockCurlConnection : public CurlConnection -{ - public: - MockCurlConnection(const ReportingHandler& handler, - Result::Code& responseCode, - std::string& getResponse, - std::string& postResponse, - bool& expectEmptyPostBody) - : CurlConnection({}, handler) - , m_responseCode(responseCode) - , m_getResponse(getResponse) - , m_postResponse(postResponse) - , m_expectEmptyPostBody(expectEmptyPostBody) - { - } - - std::string Get(const std::string&) override - { - if (m_responseCode == Result::Success) - { - INFO("MockCurlConnection::Get() called, response: " << m_getResponse); - return m_getResponse; - } - throw SFSException(m_responseCode); - } - - std::string Post(const std::string&, const std::string& data) override - { - if (m_responseCode == Result::Success) - { - INFO("MockCurlConnection::Post() called, response: " << m_postResponse); - if (m_expectEmptyPostBody) - { - REQUIRE(data.empty()); - } - else - { - REQUIRE(!data.empty()); - } - return m_postResponse; - } - throw SFSException(m_responseCode); - } - - private: - Result::Code& m_responseCode; - std::string& m_getResponse; - std::string& m_postResponse; - bool& m_expectEmptyPostBody; -}; - -void CheckProduct(const VersionEntity& entity, std::string_view ns, std::string_view name, std::string_view version) -{ - REQUIRE(entity.GetContentType() == ContentType::Generic); - REQUIRE(entity.contentId.nameSpace == ns); - REQUIRE(entity.contentId.name == name); - REQUIRE(entity.contentId.version == version); -} - -void CheckDownloadInfo(const FileEntities& files, const std::string& name) -{ - REQUIRE(files.size() == 2); - REQUIRE(files[0]->fileId == (name + ".json")); - REQUIRE(files[0]->url == ("http://localhost/1.json")); - REQUIRE(files[1]->fileId == (name + ".bin")); - REQUIRE(files[1]->url == ("http://localhost/2.bin")); -} -} // namespace - -TEST("Testing class SFSClientImpl()") -{ - const std::string ns = "testNameSpace"; - SFSClientImpl sfsClient({"testAccountId", "testInstanceId", ns, LogCallbackToTest}); - - Result::Code responseCode = Result::Success; - std::string getResponse; - std::string postResponse; - bool expectEmptyPostBody = true; - std::unique_ptr connection = std::make_unique(sfsClient.GetReportingHandler(), - responseCode, - getResponse, - postResponse, - expectEmptyPostBody); - - const std::string productName = "productName"; - const std::string expectedVersion = "0.0.0.2"; - - SECTION("Testing SFSClientImpl::GetLatestVersion()") - { - expectEmptyPostBody = false; - std::unique_ptr entity; - - SECTION("Expected response") - { - const json latestVersionResponse = { - {"ContentId", {{"Namespace", ns}, {"Name", productName}, {"Version", expectedVersion}}}}; - postResponse = latestVersionResponse.dump(); - SECTION("No attributes") - { - REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({productName, {}}, *connection)); - REQUIRE(entity); - CheckProduct(*entity, ns, productName, expectedVersion); - } - - SECTION("With attributes") - { - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({productName, attributes}, *connection)); - REQUIRE(entity); - CheckProduct(*entity, ns, productName, expectedVersion); - } - - SECTION("Failing") - { - responseCode = Result::HttpNotFound; - REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", {}}, *connection), HttpNotFound); - REQUIRE(!entity); - - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", attributes}, *connection), - HttpNotFound); - REQUIRE(!entity); - } - } - - SECTION("Unexpected response") - { - SECTION("Wrong ns") - { - const json latestVersionResponse = { - {"ContentId", {{"Namespace", "wrong"}, {"Name", productName}, {"Version", expectedVersion}}}}; - postResponse = latestVersionResponse.dump(); - } - - SECTION("Wrong name") - { - const json latestVersionResponse = { - {"ContentId", {{"Namespace", ns}, {"Name", "wrong"}, {"Version", expectedVersion}}}}; - postResponse = latestVersionResponse.dump(); - } - - REQUIRE_THROWS_CODE_MSG(entity = sfsClient.GetLatestVersion({productName, {}}, *connection), - ServiceInvalidResponse, - "Response does not match the requested product"); - REQUIRE(!entity); - } - } - - SECTION("Testing SFSClientImpl::GetLatestVersionBatch()") - { - expectEmptyPostBody = false; - json latestVersionResponse = json::array(); - latestVersionResponse.push_back( - {{"ContentId", {{"Namespace", ns}, {"Name", productName}, {"Version", expectedVersion}}}}); - postResponse = latestVersionResponse.dump(); - VersionEntities entities; - SECTION("No attributes") - { - REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{productName, {}}}, *connection)); - REQUIRE(!entities.empty()); - CheckProduct(*entities[0], ns, productName, expectedVersion); - } - - SECTION("With attributes") - { - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{productName, attributes}}, *connection)); - REQUIRE(!entities.empty()); - CheckProduct(*entities[0], ns, productName, expectedVersion); - } - - SECTION("Failing") - { - responseCode = Result::HttpNotFound; - REQUIRE_THROWS_CODE(entities = sfsClient.GetLatestVersionBatch({{"badName", {}}}, *connection), - HttpNotFound); - - const TargetingAttributes attributes{{"attr1", "value"}}; - REQUIRE_THROWS_CODE(entities = sfsClient.GetLatestVersionBatch({{"badName", attributes}}, *connection), - HttpNotFound); - } - } - - SECTION("Testing SFSClientImpl::GetSpecificVersion()") - { - json specificVersionResponse; - specificVersionResponse["ContentId"] = {{"Namespace", ns}, {"Name", productName}, {"Version", expectedVersion}}; - specificVersionResponse["Files"] = json::array({productName + ".json", productName + ".bin"}); - getResponse = specificVersionResponse.dump(); - std::unique_ptr entity; - SECTION("Getting version") - { - REQUIRE_NOTHROW(entity = sfsClient.GetSpecificVersion(productName, expectedVersion, *connection)); - REQUIRE(entity); - CheckProduct(*entity, ns, productName, expectedVersion); - } - - SECTION("Failing") - { - responseCode = Result::HttpNotFound; - REQUIRE_THROWS_CODE(entity = sfsClient.GetSpecificVersion(productName, expectedVersion, *connection), - HttpNotFound); - } - } - - SECTION("Testing SFSClientImpl::GetDownloadInfo()") - { - expectEmptyPostBody = true; - json downloadInfoResponse; - downloadInfoResponse = json::array(); - downloadInfoResponse.push_back({{"Url", "http://localhost/1.json"}, - {"FileId", productName + ".json"}, - {"SizeInBytes", 100}, - {"Hashes", {{"Sha1", "123"}, {"Sha256", "456"}}}}); - downloadInfoResponse[0]["DeliveryOptimization"] = {{"CatalogId", "789"}}; - downloadInfoResponse[0]["DeliveryOptimization"]["Properties"] = { - {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/1.json"}, {"HashOfHashes", "abc"}}}}; - - downloadInfoResponse.push_back({{"Url", "http://localhost/2.bin"}, - {"FileId", productName + ".bin"}, - {"SizeInBytes", 200}, - {"Hashes", {{"Sha1", "421"}, {"Sha256", "132"}}}}); - downloadInfoResponse[1]["DeliveryOptimization"] = downloadInfoResponse[0]["DeliveryOptimization"]; - postResponse = downloadInfoResponse.dump(); - - FileEntities files; - SECTION("Getting version") - { - REQUIRE_NOTHROW(files = sfsClient.GetDownloadInfo(productName, expectedVersion, *connection)); - REQUIRE(!files.empty()); - CheckDownloadInfo(files, productName); - } - - SECTION("Failing") - { - responseCode = Result::HttpNotFound; - REQUIRE_THROWS_CODE(files = sfsClient.GetDownloadInfo(productName, expectedVersion, *connection), - HttpNotFound); - REQUIRE(files.empty()); - } - } -} - -TEST("Testing ClientConfig validation") -{ - SECTION("accountId must not be empty") - { - const std::string expectedErrorMsg = "ClientConfig::accountId must not be empty"; - for (size_t i = 0; i <= 10; ++i) - { - ClientConfig config; - config.accountId = std::string(i, 'a'); - if (i >= 1) - { - REQUIRE_NOTHROW(SFSClientImpl(std::move(config))); - } - else - { - REQUIRE_THROWS_CODE_MSG(SFSClientImpl(std::move(config)), - InvalidArg, - expectedErrorMsg); - } - } - } - - SECTION("instanceId must not be empty") - { - const std::string expectedErrorMsg = "ClientConfig::instanceId must not be empty"; - for (size_t i = 0; i <= 10; ++i) - { - ClientConfig config; - config.accountId = "testAccountId"; - config.instanceId = std::string(i, 'a'); - if (i >= 1) - { - REQUIRE_NOTHROW(SFSClientImpl(std::move(config))); - } - else - { - REQUIRE_THROWS_CODE_MSG(SFSClientImpl(std::move(config)), - InvalidArg, - expectedErrorMsg); - } - } - } - - SECTION("NameSpace must not be empty") - { - const std::string expectedErrorMsg = "ClientConfig::nameSpace must not be empty"; - for (size_t i = 0; i <= 10; ++i) - { - ClientConfig config; - config.accountId = "testAccountId"; - config.nameSpace = std::string(i, 'a'); - if (i >= 1) - { - REQUIRE_NOTHROW(SFSClientImpl(std::move(config))); - } - else - { - REQUIRE_THROWS_CODE_MSG(SFSClientImpl(std::move(config)), - InvalidArg, - expectedErrorMsg); - } - } - } -} - -TEST("Testing SFSClientImpl::SetCustomBaseUrl()") -{ - ClientConfig config; - config.accountId = "testAccountId"; - SFSClientImpl sfsClient(std::move(config)); - - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/"); - - sfsClient.SetCustomBaseUrl("customUrl"); - REQUIRE_THROWS_CODE_MSG(sfsClient.MakeUrlBuilder().GetUrl(), - ConnectionUrlSetupFailed, - "Curl URL error: Bad scheme"); - - sfsClient.SetCustomBaseUrl("http://customUrl.com/"); - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/"); -} - -TEST("Testing test override SFS_TEST_OVERRIDE_BASE_URL") -{ - ClientConfig config; - config.accountId = "testAccountId"; - SFSClientImpl sfsClient(std::move(config)); - - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/"); - - { - INFO("Can override the base url with the test key"); - ScopedTestOverride override(TestOverride::BaseUrl, "http://override.com"); - if (AreTestOverridesAllowed()) - { - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://override.com/"); - } - else - { - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/"); - } - } - - if (AreTestOverridesAllowed()) - { - INFO("Fails if the override is not a valid URL"); - ScopedTestOverride override(TestOverride::BaseUrl, "override"); - { - REQUIRE_THROWS_CODE_MSG(sfsClient.MakeUrlBuilder().GetUrl(), - ConnectionUrlSetupFailed, - "Curl URL error: Bad scheme"); - } - } - - INFO("Override is unset after ScopedEnv goes out of scope"); - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/"); - - sfsClient.SetCustomBaseUrl("http://customUrl.com"); - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/"); - - { - INFO("Can also override a custom base base url with the test key"); - ScopedTestOverride override(TestOverride::BaseUrl, "http://override.com"); - if (AreTestOverridesAllowed()) - { - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://override.com/"); - } - else - { - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/"); - } - } - - REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/"); -} - -TEST("Testing passing a logging callback to constructor of SFSClientImpl") -{ - SFSClientImpl sfsClient( - {"testAccountId", "testInstanceId", "testNameSpace", [](const LogData&) {}}); - SFSClientImpl sfsClient2({"testAccountId", "testInstanceId", "testNameSpace", nullptr}); -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/SFSUrlBuilderTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/SFSUrlBuilderTests.cpp deleted file mode 100644 index 7e4810545c..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/SFSUrlBuilderTests.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../util/SFSExceptionMatcher.h" -#include "../../util/TestHelper.h" -#include "ReportingHandler.h" -#include "SFSUrlBuilder.h" - -#include - -#define TEST(...) TEST_CASE("[SFSUrlBuilderTests] " __VA_ARGS__) - -using namespace SFS::details; -using namespace SFS::test; - -const std::string c_accountId = "accountId"; -const std::string c_instanceId = "instanceId"; -const std::string c_nameSpace = "nameSpace"; - -TEST("SFSUrlBuilder") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - SECTION("CreateFromAccountId()") - { - SECTION("ASCII strings") - { - SFSUrlBuilder builder(c_accountId, c_instanceId, c_nameSpace, handler); - REQUIRE(builder.GetUrl() == "https://accountId.api.cdp.microsoft.com/"); - - REQUIRE( - builder.GetLatestVersionUrl("product") == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId/namespaces/nameSpace/names/product/versions/latest?action=select"); - - REQUIRE( - builder.GetLatestVersionBatchUrl() == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId/namespaces/nameSpace/names?action=BatchUpdates"); - - REQUIRE( - builder.GetSpecificVersionUrl("product", "version") == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId/namespaces/nameSpace/names/product/versions/version"); - - REQUIRE( - builder.GetDownloadInfoUrl("product", "version") == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId/namespaces/nameSpace/names/product/versions/version/files?action=GenerateDownloadInfo"); - } - - SECTION("Non-ASCII strings") - { - REQUIRE_THROWS_CODE_MSG(SFSUrlBuilder("a&b", c_instanceId, c_nameSpace, handler), - ConnectionUrlSetupFailed, - "Curl URL error: Bad hostname"); - - REQUIRE_THROWS_CODE_MSG(SFSUrlBuilder("a\nb", c_instanceId, c_nameSpace, handler), - ConnectionUrlSetupFailed, - "Curl URL error: Bad hostname"); - - REQUIRE_THROWS_CODE_MSG(SFSUrlBuilder("a\tb", c_instanceId, c_nameSpace, handler), - ConnectionUrlSetupFailed, - "Curl URL error: Bad hostname"); - - SFSUrlBuilder builder(c_accountId, "instanceId@", "namespace+", handler); - REQUIRE(builder.GetUrl() == "https://accountId.api.cdp.microsoft.com/"); - - REQUIRE( - builder.GetLatestVersionUrl("pr$duct") == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId%40/namespaces/namespace%2b/names/pr%24duct/versions/latest?action=select"); - - REQUIRE( - builder.GetLatestVersionBatchUrl() == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId%40/namespaces/namespace%2b/names?action=BatchUpdates"); - - REQUIRE( - builder.GetSpecificVersionUrl("pr$duct", "versi/n") == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId%40/namespaces/namespace%2b/names/pr%24duct/versions/versi%2fn"); - - REQUIRE( - builder.GetDownloadInfoUrl("pr$duct", "versi/n") == - "https://accountId.api.cdp.microsoft.com/api/v2/contents/instanceId%40/namespaces/namespace%2b/names/pr%24duct/versions/versi%2fn/files?action=GenerateDownloadInfo"); - } - } - - SECTION("CreateFromCustomUrl()") - { - SFSUrlBuilder builder(SFSCustomUrl("http://www.example.com"), c_instanceId, c_nameSpace, handler); - REQUIRE(builder.GetUrl() == "http://www.example.com/"); - - SFSUrlBuilder builder2(SFSCustomUrl("http://www.example2.com"), c_instanceId, c_nameSpace, handler); - REQUIRE(builder2.GetUrl() == "http://www.example2.com/"); - - REQUIRE_THROWS_CODE_MSG(SFSUrlBuilder(SFSCustomUrl("http://www.+.com"), c_instanceId, c_nameSpace, handler), - ConnectionUrlSetupFailed, - "Curl URL error: Bad hostname"); - - REQUIRE_THROWS_CODE_MSG(SFSUrlBuilder(SFSCustomUrl("example"), c_instanceId, c_nameSpace, handler), - ConnectionUrlSetupFailed, - "Curl URL error: Bad scheme"); - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/TestOverrideTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/TestOverrideTests.cpp deleted file mode 100644 index a2b710e63c..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/TestOverrideTests.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Env.h" -#include "TestOverride.h" - -#include - -#define TEST(...) TEST_CASE("[TestOverrideTests] " __VA_ARGS__) - -using namespace SFS::test; -using namespace SFS::details::env; - -TEST("Testing AreTestOverridesAllowed()") -{ - bool areTestOverridesAllowed = AreTestOverridesAllowed(); -#ifdef SFS_ENABLE_TEST_OVERRIDES - REQUIRE(areTestOverridesAllowed); -#else - REQUIRE_FALSE(areTestOverridesAllowed); -#endif -} - -TEST("Testing GetEnvVarNameFromOverride") -{ - REQUIRE(GetEnvVarNameFromOverride(TestOverride::BaseUrl) == "SFS_TEST_OVERRIDE_BASE_URL"); -} - -TEST("Testing GetTestOverride()") -{ - if (AreTestOverridesAllowed()) - { - SECTION("Testing GetTestOverride() on a non-existing environment variable") - { - auto env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(!env.has_value()); - - SECTION("Testing GetTestOverride() on an existing environment variable") - { - const std::string varName = GetEnvVarNameFromOverride(TestOverride::BaseUrl); - REQUIRE(SetEnv(varName, "override")); - - env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(env.has_value()); - REQUIRE(*env == "override"); - - INFO("Unsetting the environment variable"); - REQUIRE(UnsetEnv(varName)); - } - } - } - else - { - SECTION("GetTestOverride() returns std::nullopt when test overrides are not allowed") - { - auto env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(!env.has_value()); - } - } -} - -TEST("Testing ScopedTestOverride") -{ - SECTION("Testing ScopedTestOverride on a non-existing override") - { - const std::string varName = GetEnvVarNameFromOverride(TestOverride::BaseUrl); - auto env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(!env.has_value()); - - { - ScopedTestOverride scopedOverride(TestOverride::BaseUrl, "dummyValue"); - - INFO("Variable exists within scope"); - env = GetEnv(varName); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue"); - - if (AreTestOverridesAllowed()) - { - INFO("Checking test override within scope"); - env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue"); - } - - SECTION("Testing ScopedEnv on an existing variable overwrites it") - { - { - ScopedTestOverride scopedOverride2(TestOverride::BaseUrl, "dummyValue2"); - - INFO("Variable has value overwritten within scope"); - env = GetEnv(varName); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue2"); - - if (AreTestOverridesAllowed()) - { - INFO("Checking test override has been overwritten within scope"); - env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue2"); - } - } - - INFO("Variable goes back to previous value"); - env = GetEnv(varName); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue"); - - if (AreTestOverridesAllowed()) - { - INFO("Checking test override has gone back to previous value"); - env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(env.has_value()); - REQUIRE(*env == "dummyValue"); - } - } - } - - INFO("Variable should be unset after the scope ends"); - env = GetEnv(varName); - REQUIRE(!env.has_value()); - - if (AreTestOverridesAllowed()) - { - INFO("Test override should be unset after the scope ends"); - env = GetTestOverride(TestOverride::BaseUrl); - REQUIRE(!env.has_value()); - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/UrlBuilderTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/UrlBuilderTests.cpp deleted file mode 100644 index d61336bec5..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/UrlBuilderTests.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../util/SFSExceptionMatcher.h" -#include "../../util/TestHelper.h" -#include "ReportingHandler.h" -#include "UrlBuilder.h" - -#include - -#define TEST(...) TEST_CASE("[UrlBuilderTests] " __VA_ARGS__) - -using namespace SFS::details; -using namespace SFS::test; - -TEST("UrlBuilder") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - UrlBuilder builder(handler); - REQUIRE_THROWS_CODE_MSG(builder.GetUrl(), ConnectionUrlSetupFailed, "Curl URL error: No host part in the URL"); - - builder.SetHost("www.example.com"); - REQUIRE_THROWS_CODE_MSG(builder.GetUrl(), ConnectionUrlSetupFailed, "Curl URL error: No scheme part in the URL"); - - builder.SetScheme(Scheme::Https); - REQUIRE(builder.GetUrl() == "https://www.example.com/"); - - SECTION("SetHost") - { - builder.SetHost("www.example2.com"); - REQUIRE(builder.GetUrl() == "https://www.example2.com/"); - - REQUIRE_THROWS_CODE_MSG(builder.SetHost("+"), ConnectionUrlSetupFailed, "Curl URL error: Bad hostname"); - REQUIRE_THROWS_CODE_MSG(builder.SetHost("a&b"), ConnectionUrlSetupFailed, "Curl URL error: Bad hostname"); - REQUIRE_THROWS_CODE_MSG(builder.SetHost("a\nb"), ConnectionUrlSetupFailed, "Curl URL error: Bad hostname"); - REQUIRE_THROWS_CODE_MSG(builder.SetHost("a\tb"), ConnectionUrlSetupFailed, "Curl URL error: Bad hostname"); - - REQUIRE_THROWS_CODE_MSG(builder.SetHost(""), InvalidArg, "Host must not empty"); - } - - SECTION("SetPath") - { - builder.SetPath("index.html"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html"); - - REQUIRE_THROWS_CODE_MSG(builder.SetPath(""), InvalidArg, "Path must not empty"); - - builder.ResetPath(); - REQUIRE(builder.GetUrl() == "https://www.example.com/"); - } - - SECTION("AppendPath") - { - builder.SetPath("index.html"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html"); - - builder.AppendPathEncoded("index.html"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html/index.html"); - - builder.AppendPath("a/"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html/index.html/a/"); - - builder.AppendPath("b/"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html/index.html/a/b/"); - - INFO("Encoding for append includes the / character"); - builder.AppendPathEncoded("c/"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html/index.html/a/b/c%2f"); - - INFO("Calling SetPath() resets the path"); - builder.SetPath("index"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index"); - - REQUIRE_THROWS_CODE_MSG(builder.AppendPath(""), InvalidArg, "Path must not empty"); - } - - SECTION("SetQuery, AppendQuery") - { - builder.SetQuery("key", "value"); - REQUIRE(builder.GetUrl() == "https://www.example.com/?key=value"); - - builder.AppendQuery("key2", "value2"); - REQUIRE(builder.GetUrl() == "https://www.example.com/?key=value&key2=value2"); - - builder.SetQuery("key2", "value2"); - REQUIRE(builder.GetUrl() == "https://www.example.com/?key2=value2"); - - builder.AppendQuery("key3", "valu/e@2"); - REQUIRE(builder.GetUrl() == "https://www.example.com/?key2=value2&key3=valu%2fe%402"); - - builder.SetQuery("ke$y4", "valu/e@3"); - REQUIRE(builder.GetUrl() == "https://www.example.com/?ke%24y4=valu%2fe%403"); - - REQUIRE_THROWS_CODE_MSG(builder.SetQuery("", "value"), InvalidArg, "Query key and value must not empty"); - REQUIRE_THROWS_CODE_MSG(builder.SetQuery("key", ""), InvalidArg, "Query key and value must not empty"); - REQUIRE_THROWS_CODE_MSG(builder.SetQuery("", ""), InvalidArg, "Query key and value must not empty"); - - REQUIRE_THROWS_CODE_MSG(builder.AppendQuery("", "value"), InvalidArg, "Query key and value must not empty"); - REQUIRE_THROWS_CODE_MSG(builder.AppendQuery("key", ""), InvalidArg, "Query key and value must not empty"); - REQUIRE_THROWS_CODE_MSG(builder.AppendQuery("", ""), InvalidArg, "Query key and value must not empty"); - - builder.ResetQuery(); - REQUIRE(builder.GetUrl() == "https://www.example.com/"); - } - - SECTION("SetUrl") - { - builder.SetUrl("https://www.example.com/index.html?key=value"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html?key=value"); - REQUIRE_THROWS_CODE_MSG(builder.SetUrl("https://www.+.com"), - ConnectionUrlSetupFailed, - "Curl URL error: Bad hostname"); - - REQUIRE_THROWS_CODE_MSG(builder.SetUrl(""), InvalidArg, "Url must not empty"); - } - - SECTION("SetScheme, SetHost, SetPath, SetQuery") - { - builder.SetScheme(Scheme::Https).SetHost("www.example.com").SetPath("index.html").SetQuery("key", "value"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index.html?key=value"); - } - - SECTION("SetScheme, SetHost, AppendPathEncoded, SetQuery") - { - builder.SetScheme(Scheme::Https) - .SetHost("www.example.com") - .AppendPathEncoded("index@.html") - .SetQuery("key", "value"); - REQUIRE(builder.GetUrl() == "https://www.example.com/index%40.html?key=value"); - } - - SECTION("Constructor with URL") - { - UrlBuilder builder2("https://www.example.com/index.html?key=value", handler); - REQUIRE(builder2.GetUrl() == "https://www.example.com/index.html?key=value"); - - REQUIRE_THROWS_CODE_MSG(UrlBuilder("https://www.+.com", handler), - ConnectionUrlSetupFailed, - "Curl URL error: Bad hostname"); - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/UtilTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/UtilTests.cpp deleted file mode 100644 index a91e0a1e7e..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/UtilTests.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "Util.h" - -#include - -#define TEST(...) TEST_CASE("[UtilTests] " __VA_ARGS__) - -using namespace SFS::details::util; - -TEST("Testing AreEqualI") -{ - SECTION("ASCII strings") - { - REQUIRE(AreEqualI("abc", "abc")); - REQUIRE(AreEqualI("abc", "ABC")); - REQUIRE(AreEqualI("abc", "Abc")); - REQUIRE(AreEqualI("abc", "aBc")); - REQUIRE(AreEqualI("abc", "abC")); - REQUIRE(AreEqualI("abc", "ABC")); - REQUIRE(AreEqualI("abc", "AbC")); - REQUIRE(AreEqualI("abc", "aBC")); - } -} - -TEST("Testing AreNotEqualI") -{ - REQUIRE(AreNotEqualI("abc", "ab")); - REQUIRE(AreNotEqualI("ab", "abc")); - REQUIRE(AreNotEqualI("abc", "abd")); -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/entity/FileEntityTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/entity/FileEntityTests.cpp deleted file mode 100644 index 84ea724cf2..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/entity/FileEntityTests.cpp +++ /dev/null @@ -1,639 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../../util/SFSExceptionMatcher.h" -#include "../../../util/TestHelper.h" -#include "ReportingHandler.h" -#include "entity/FileEntity.h" -#include "sfsclient/AppFile.h" -#include "sfsclient/File.h" - -#include -#include - -#define TEST(...) TEST_CASE("[FileEntityTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; -using json = nlohmann::json; - -// GenericFileEntity constants -const std::string c_fileId = "fileId"; -const std::string c_url = "url"; -const uint64_t c_size = 123; -const std::string c_sha1 = "sha1"; -const std::string c_sha256 = "sha256"; - -// AppFileEntity constants -const std::string c_fileMoniker = "fileMoniker"; -const std::string c_arch = "amd64"; -const std::string c_applicability = "app"; -ApplicabilityDetailsEntity c_appDetailsEntity{{c_arch}, {c_applicability}}; - -TEST("Testing FileEntity::FromJson()") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - SECTION("Generic File Entity") - { - std::unique_ptr entity; - SECTION("Correct") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - - REQUIRE_NOTHROW(entity = FileEntity::FromJson(fileEntity, handler)); - REQUIRE(entity != nullptr); - REQUIRE(entity->GetContentType() == ContentType::Generic); - REQUIRE(entity->fileId == c_fileId); - REQUIRE(entity->url == c_url); - REQUIRE(entity->sizeInBytes == c_size); - REQUIRE(entity->hashes.size() == 2); - REQUIRE(entity->hashes.at("Sha1") == c_sha1); - REQUIRE(entity->hashes.at("Sha256") == c_sha256); - } - - SECTION("Missing fields") - { - SECTION("Missing FileId") - { - const json fileEntity = {{"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "Missing File.FileId in response"); - } - - SECTION("Missing Url") - { - const json fileEntity = {{"FileId", c_fileId}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "Missing File.Url in response"); - } - - SECTION("Missing SizeInBytes") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "Missing File.SizeInBytes in response"); - } - - SECTION("Missing Hashes") - { - const json fileEntity = {{"FileId", c_fileId}, {"Url", c_url}, {"SizeInBytes", c_size}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "Missing File.Hashes in response"); - } - } - - SECTION("Wrong types") - { - SECTION("FileId") - { - const json fileEntity = {{"FileId", 1}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.FileId is not a string"); - } - - SECTION("Url") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", 1}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.Url is not a string"); - } - - SECTION("SizeInBytes") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", "size"}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.SizeInBytes is not an unsigned number"); - } - - SECTION("Hashes not an object") - { - const json fileEntity = {{"FileId", c_fileId}, {"Url", c_url}, {"SizeInBytes", c_size}, {"Hashes", 1}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.Hashes is not an object"); - } - - SECTION("Hashes.Sha1") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", 1}}}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.Hashes object value is not a string"); - } - } - } - - SECTION("App File Entity") - { - std::unique_ptr entity; - const json applicabilityDetails = {{"Architectures", json::array({c_arch})}, - {"PlatformApplicabilityForPackage", json::array({c_applicability})}}; - - SECTION("Correct") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", applicabilityDetails}}; - - REQUIRE_NOTHROW(entity = FileEntity::FromJson(fileEntity, handler)); - REQUIRE(entity != nullptr); - REQUIRE(entity->GetContentType() == ContentType::App); - REQUIRE(entity->fileId == c_fileId); - REQUIRE(entity->url == c_url); - REQUIRE(entity->sizeInBytes == c_size); - REQUIRE(entity->hashes.size() == 2); - REQUIRE(entity->hashes.at("Sha1") == c_sha1); - REQUIRE(entity->hashes.at("Sha256") == c_sha256); - - AppFileEntity* appEntity = dynamic_cast(entity.get()); - REQUIRE(appEntity->fileMoniker == c_fileMoniker); - REQUIRE(appEntity->applicabilityDetails.architectures.size() == 1); - REQUIRE(appEntity->applicabilityDetails.architectures[0] == c_arch); - REQUIRE(appEntity->applicabilityDetails.platformApplicabilityForPackage.size() == 1); - REQUIRE(appEntity->applicabilityDetails.platformApplicabilityForPackage[0] == c_applicability); - } - - SECTION("Missing fields") - { - SECTION("Missing ApplicabilityDetails") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "Missing File.ApplicabilityDetails in response"); - } - - SECTION("Missing ApplicabilityDetails.Architectures") - { - const json wrongApplicabilityDetails = { - {"PlatformApplicabilityForPackage", json::array({c_applicability})}}; - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", wrongApplicabilityDetails}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "Missing File.ApplicabilityDetails.Architectures in response"); - } - - SECTION("Missing ApplicabilityDetails.PlatformApplicabilityForPackage") - { - const json wrongApplicabilityDetails = {{"Architectures", json::array({c_arch})}}; - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", wrongApplicabilityDetails}}; - REQUIRE_THROWS_CODE_MSG( - FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "Missing File.ApplicabilityDetails.PlatformApplicabilityForPackage in response"); - } - } - - SECTION("Wrong types") - { - SECTION("FileMoniker") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", 1}, - {"ApplicabilityDetails", applicabilityDetails}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.FileMoniker is not a string"); - } - - SECTION("ApplicabilityDetails") - { - SECTION("Not an object") - { - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", c_fileId}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.ApplicabilityDetails is not an object"); - } - - SECTION("Architectures is not an array") - { - const json wrongApplicabilityDetails = { - {"Architectures", c_fileId}, - {"PlatformApplicabilityForPackage", json::array({c_applicability})}}; - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", wrongApplicabilityDetails}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.ApplicabilityDetails.Architectures is not an array"); - } - - SECTION("Architectures array value is not a string") - { - const json wrongApplicabilityDetails = { - {"Architectures", json::array({1})}, - {"PlatformApplicabilityForPackage", json::array({c_applicability})}}; - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", wrongApplicabilityDetails}}; - REQUIRE_THROWS_CODE_MSG(FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.ApplicabilityDetails.Architectures array value is not a string"); - } - - SECTION("PlatformApplicabilityForPackage is not an array") - { - const json wrongApplicabilityDetails = {{"Architectures", json::array({c_arch})}, - {"PlatformApplicabilityForPackage", c_fileId}}; - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", wrongApplicabilityDetails}}; - REQUIRE_THROWS_CODE_MSG( - FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.ApplicabilityDetails.PlatformApplicabilityForPackage is not an array"); - } - - SECTION("PlatformApplicabilityForPackage array value is not a string") - { - const json wrongApplicabilityDetails = {{"Architectures", json::array({c_arch})}, - {"PlatformApplicabilityForPackage", json::array({1})}}; - const json fileEntity = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", wrongApplicabilityDetails}}; - REQUIRE_THROWS_CODE_MSG( - FileEntity::FromJson(fileEntity, handler), - ServiceInvalidResponse, - "File.ApplicabilityDetails.PlatformApplicabilityForPackage array value is not a string"); - } - } - } - } -} - -TEST("Testing GenericFileEntity conversions") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - auto CheckFile = [&](const File& file) { - REQUIRE(file.GetFileId() == c_fileId); - REQUIRE(file.GetUrl() == c_url); - REQUIRE(file.GetSizeInBytes() == c_size); - REQUIRE(file.GetHashes().size() == 2); - REQUIRE(file.GetHashes().at(HashType::Sha1) == c_sha1); - REQUIRE(file.GetHashes().at(HashType::Sha256) == c_sha256); - }; - - SECTION("GenericFileEntity::ToFile()") - { - SECTION("Success") - { - std::unique_ptr entity = std::make_unique(); - entity->fileId = c_fileId; - entity->url = c_url; - entity->sizeInBytes = c_size; - entity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - - auto file = GenericFileEntity::ToFile(std::move(*entity), handler); - CheckFile(*file); - } - - SECTION("Wrong type") - { - std::unique_ptr wrongEntity = std::make_unique(); - wrongEntity->fileId = c_fileId; - wrongEntity->url = c_url; - wrongEntity->sizeInBytes = c_size; - wrongEntity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - - REQUIRE_THROWS_CODE_MSG( - GenericFileEntity::ToFile(std::move(*wrongEntity), handler), - ServiceUnexpectedContentType, - R"(The service returned file "fileId" with content type [App] while the expected type was [Generic])"); - } - } - - SECTION("GenericFileEntity::FileEntitiesToFileVector()") - { - SECTION("Success") - { - std::unique_ptr entity = std::make_unique(); - entity->fileId = c_fileId; - entity->url = c_url; - entity->sizeInBytes = c_size; - entity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - - FileEntities entities; - entities.push_back(std::move(entity)); - - std::vector files; - SECTION("1 file") - { - REQUIRE_NOTHROW(files = GenericFileEntity::FileEntitiesToFileVector(std::move(entities), handler)); - REQUIRE(files.size() == 1); - CheckFile(files[0]); - } - - SECTION("2 files") - { - std::unique_ptr entity2 = std::make_unique(); - entity2->fileId = c_fileId; - entity2->url = c_url; - entity2->sizeInBytes = c_size; - entity2->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - - entities.push_back(std::move(entity2)); - REQUIRE_NOTHROW(files = GenericFileEntity::FileEntitiesToFileVector(std::move(entities), handler)); - REQUIRE(files.size() == 2); - CheckFile(files[0]); - CheckFile(files[1]); - } - } - - SECTION("Wrong type, fails") - { - std::unique_ptr wrongEntity = std::make_unique(); - wrongEntity->fileId = c_fileId; - wrongEntity->url = c_url; - wrongEntity->sizeInBytes = c_size; - wrongEntity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - - FileEntities wrongEntities; - wrongEntities.push_back(std::move(wrongEntity)); - REQUIRE_THROWS_CODE_MSG( - GenericFileEntity::FileEntitiesToFileVector(std::move(wrongEntities), handler), - ServiceUnexpectedContentType, - R"(The service returned file "fileId" with content type [App] while the expected type was [Generic])"); - } - } -} - -TEST("Testing AppFileEntity conversions") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - auto CheckAppFile = [&](const AppFile& file) { - REQUIRE(file.GetFileId() == c_fileId); - REQUIRE(file.GetUrl() == c_url); - REQUIRE(file.GetSizeInBytes() == c_size); - REQUIRE(file.GetHashes().size() == 2); - REQUIRE(file.GetHashes().at(HashType::Sha1) == c_sha1); - REQUIRE(file.GetHashes().at(HashType::Sha256) == c_sha256); - REQUIRE(file.GetFileMoniker() == c_fileMoniker); - REQUIRE(file.GetApplicabilityDetails().GetArchitectures().size() == 1); - REQUIRE(file.GetApplicabilityDetails().GetArchitectures()[0] == Architecture::Amd64); - REQUIRE(file.GetApplicabilityDetails().GetPlatformApplicabilityForPackage().size() == 1); - REQUIRE(file.GetApplicabilityDetails().GetPlatformApplicabilityForPackage()[0] == c_applicability); - }; - - SECTION("AppFileEntity::ToAppFile()") - { - SECTION("Success") - { - std::unique_ptr entity = std::make_unique(); - auto appEntity = dynamic_cast(entity.get()); - appEntity->fileId = c_fileId; - appEntity->url = c_url; - appEntity->sizeInBytes = c_size; - appEntity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - appEntity->fileMoniker = c_fileMoniker; - appEntity->applicabilityDetails = c_appDetailsEntity; - - auto file = AppFileEntity::ToAppFile(std::move(*entity), handler); - CheckAppFile(*file); - } - - SECTION("Wrong type") - { - std::unique_ptr wrongEntity = std::make_unique(); - wrongEntity->fileId = c_fileId; - wrongEntity->url = c_url; - wrongEntity->sizeInBytes = c_size; - wrongEntity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - - REQUIRE_THROWS_CODE_MSG( - AppFileEntity::ToAppFile(std::move(*wrongEntity), handler), - ServiceUnexpectedContentType, - R"(The service returned file "fileId" with content type [Generic] while the expected type was [App])"); - } - } - - SECTION("Testing AppFileEntity::FileEntitiesToAppFileVector()") - { - SECTION("Success") - { - std::unique_ptr entity = std::make_unique(); - auto appEntity = dynamic_cast(entity.get()); - appEntity->fileId = c_fileId; - appEntity->url = c_url; - appEntity->sizeInBytes = c_size; - appEntity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - appEntity->fileMoniker = c_fileMoniker; - appEntity->applicabilityDetails = c_appDetailsEntity; - - FileEntities entities; - entities.push_back(std::move(entity)); - - std::vector files; - SECTION("1 file") - { - REQUIRE_NOTHROW(files = AppFileEntity::FileEntitiesToAppFileVector(std::move(entities), handler)); - REQUIRE(files.size() == 1); - CheckAppFile(files[0]); - } - - SECTION("2 files") - { - std::unique_ptr entity2 = std::make_unique(); - auto appEntity2 = dynamic_cast(entity2.get()); - appEntity2->fileId = c_fileId; - appEntity2->url = c_url; - appEntity2->sizeInBytes = c_size; - appEntity2->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - appEntity2->fileMoniker = c_fileMoniker; - appEntity2->applicabilityDetails = c_appDetailsEntity; - - entities.push_back(std::move(entity2)); - REQUIRE_NOTHROW(files = AppFileEntity::FileEntitiesToAppFileVector(std::move(entities), handler)); - REQUIRE(files.size() == 2); - CheckAppFile(files[0]); - CheckAppFile(files[1]); - } - } - - SECTION("Wrong type, fails") - { - std::unique_ptr wrongEntity = std::make_unique(); - wrongEntity->fileId = c_fileId; - wrongEntity->url = c_url; - wrongEntity->sizeInBytes = c_size; - wrongEntity->hashes = {{"Sha1", c_sha1}, {"Sha256", c_sha256}}; - - FileEntities wrongEntities; - wrongEntities.push_back(std::move(wrongEntity)); - REQUIRE_THROWS_CODE_MSG( - AppFileEntity::FileEntitiesToAppFileVector(std::move(wrongEntities), handler), - ServiceUnexpectedContentType, - R"(The service returned file "fileId" with content type [Generic] while the expected type was [App])"); - } - } -} - -TEST("Testing FileEntity::DownloadInfoResponseToFileEntities()") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - SECTION("GenericFileEntity") - { - auto CheckEntity = [&](const FileEntity& entity) { - REQUIRE(entity.GetContentType() == ContentType::Generic); - REQUIRE(entity.fileId == c_fileId); - REQUIRE(entity.url == c_url); - REQUIRE(entity.sizeInBytes == c_size); - REQUIRE(entity.hashes.size() == 2); - REQUIRE(entity.hashes.at("Sha1") == c_sha1); - REQUIRE(entity.hashes.at("Sha256") == c_sha256); - }; - - SECTION("Correct") - { - const json fileJson = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}}; - - SECTION("1 file") - { - const json downloadInfoResponse = json::array({fileJson}); - auto entities = FileEntity::DownloadInfoResponseToFileEntities(downloadInfoResponse, handler); - REQUIRE(entities.size() == 1); - REQUIRE(entities[0] != nullptr); - CheckEntity(*entities[0]); - } - - SECTION("2 files") - { - const json downloadInfoResponse = json::array({fileJson, fileJson}); - auto entities = FileEntity::DownloadInfoResponseToFileEntities(downloadInfoResponse, handler); - REQUIRE(entities.size() == 2); - REQUIRE(entities[0] != nullptr); - CheckEntity(*entities[0]); - REQUIRE(entities[1] != nullptr); - CheckEntity(*entities[1]); - } - } - } - - SECTION("AppFileEntity") - { - const json applicabilityDetails = {{"Architectures", json::array({c_arch})}, - {"PlatformApplicabilityForPackage", json::array({c_applicability})}}; - - auto CheckEntity = [&](const AppFileEntity& entity) { - REQUIRE(entity.GetContentType() == ContentType::App); - REQUIRE(entity.fileId == c_fileId); - REQUIRE(entity.url == c_url); - REQUIRE(entity.sizeInBytes == c_size); - REQUIRE(entity.hashes.size() == 2); - REQUIRE(entity.hashes.at("Sha1") == c_sha1); - REQUIRE(entity.hashes.at("Sha256") == c_sha256); - REQUIRE(entity.fileMoniker == c_fileMoniker); - REQUIRE(entity.applicabilityDetails.architectures.size() == 1); - REQUIRE(entity.applicabilityDetails.architectures[0] == c_arch); - REQUIRE(entity.applicabilityDetails.platformApplicabilityForPackage.size() == 1); - REQUIRE(entity.applicabilityDetails.platformApplicabilityForPackage[0] == c_applicability); - }; - - SECTION("Correct") - { - const json fileJson = {{"FileId", c_fileId}, - {"Url", c_url}, - {"SizeInBytes", c_size}, - {"Hashes", {{"Sha1", c_sha1}, {"Sha256", c_sha256}}}, - {"FileMoniker", c_fileMoniker}, - {"ApplicabilityDetails", applicabilityDetails}}; - - SECTION("1 file") - { - const json downloadInfoResponse = json::array({fileJson}); - auto entities = FileEntity::DownloadInfoResponseToFileEntities(downloadInfoResponse, handler); - REQUIRE(entities.size() == 1); - REQUIRE(entities[0] != nullptr); - CheckEntity(dynamic_cast(*entities[0])); - } - - SECTION("2 files") - { - const json downloadInfoResponse = json::array({fileJson, fileJson}); - auto entities = FileEntity::DownloadInfoResponseToFileEntities(downloadInfoResponse, handler); - REQUIRE(entities.size() == 2); - REQUIRE(entities[0] != nullptr); - CheckEntity(dynamic_cast(*entities[0])); - REQUIRE(entities[1] != nullptr); - CheckEntity(dynamic_cast(*entities[1])); - } - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/unit/details/entity/VersionEntityTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/details/entity/VersionEntityTests.cpp deleted file mode 100644 index 23b1b4f4d3..0000000000 --- a/src/SfsClient/sfs-client/client/tests/unit/details/entity/VersionEntityTests.cpp +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "../../../util/SFSExceptionMatcher.h" -#include "../../../util/TestHelper.h" -#include "ReportingHandler.h" -#include "entity/VersionEntity.h" -#include "sfsclient/ContentId.h" - -#include -#include - -#define TEST(...) TEST_CASE("[VersionEntityTests] " __VA_ARGS__) - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; -using json = nlohmann::json; - -// GenericVersionEntity constants -const std::string c_ns = "namespace"; -const std::string c_name = "name"; -const std::string c_version = "version"; - -// AppVersionEntity constants -const std::string c_updateId = "updateId"; - -TEST("Testing VersionEntity::FromJson()") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - SECTION("Generic Version Entity") - { - std::unique_ptr entity; - SECTION("Correct") - { - const json versionEntity = {{"ContentId", {{"Namespace", c_ns}, {"Name", c_name}, {"Version", c_version}}}}; - - REQUIRE_NOTHROW(entity = VersionEntity::FromJson(versionEntity, handler)); - REQUIRE(entity != nullptr); - REQUIRE(entity->GetContentType() == ContentType::Generic); - REQUIRE(entity->contentId.nameSpace == c_ns); - REQUIRE(entity->contentId.name == c_name); - REQUIRE(entity->contentId.version == c_version); - } - - SECTION("Missing fields") - { - SECTION("Missing ContentId") - { - const json versionEntity = {{"Namespace", c_ns}, {"Name", c_name}, {"Version", c_version}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing ContentId in response"); - } - - SECTION("Missing ContentId.Namespace") - { - const json versionEntity = {{"ContentId", {{"Name", c_name}, {"Version", c_version}}}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing ContentId.Namespace in response"); - } - - SECTION("Missing ContentId.Name") - { - const json versionEntity = {{"ContentId", {{"Namespace", c_ns}, {"Version", c_version}}}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing ContentId.Name in response"); - } - - SECTION("Missing ContentId.Version") - { - const json versionEntity = {{"ContentId", {{"Namespace", c_ns}, {"Name", c_name}}}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing ContentId.Version in response"); - } - } - - SECTION("Wrong types") - { - SECTION("ContentId not an object") - { - const json versionEntity = json::array({{"Namespace", 1}, {"Name", c_name}, {"Version", c_version}}); - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Response is not a JSON object"); - } - - SECTION("ContentId.Namespace") - { - const json versionEntity = { - {"ContentId", {{"Namespace", 1}, {"Name", c_name}, {"Version", c_version}}}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "ContentId.Namespace is not a string"); - } - - SECTION("ContentId.Name") - { - const json versionEntity = {{"ContentId", {{"Namespace", c_ns}, {"Name", 1}, {"Version", c_version}}}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "ContentId.Name is not a string"); - } - - SECTION("ContentId.Version") - { - const json versionEntity = {{"ContentId", {{"Namespace", c_ns}, {"Name", c_name}, {"Version", 1}}}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "ContentId.Version is not a string"); - } - } - } - - SECTION("App Version Entity") - { - std::unique_ptr entity; - const json contentId = {{"Namespace", c_ns}, {"Name", c_name}, {"Version", c_version}}; - - SECTION("Correct") - { - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({contentId})}}; - - REQUIRE_NOTHROW(entity = VersionEntity::FromJson(versionEntity, handler)); - REQUIRE(entity != nullptr); - REQUIRE(entity->GetContentType() == ContentType::App); - REQUIRE(entity->contentId.nameSpace == c_ns); - REQUIRE(entity->contentId.name == c_name); - REQUIRE(entity->contentId.version == c_version); - - AppVersionEntity* appEntity = dynamic_cast(entity.get()); - REQUIRE(appEntity->updateId == c_updateId); - REQUIRE(appEntity->prerequisites.size() == 1); - REQUIRE(appEntity->prerequisites[0].contentId.nameSpace == c_ns); - REQUIRE(appEntity->prerequisites[0].contentId.name == c_name); - REQUIRE(appEntity->prerequisites[0].contentId.version == c_version); - } - - SECTION("Missing fields") - { - SECTION("Missing Prerequisites") - { - const json versionEntity = {{"ContentId", contentId}, {"UpdateId", c_updateId}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing Prerequisites in response"); - } - - SECTION("Missing Prerequisite.Namespace") - { - const json wrongPrerequisite = {{"Name", c_name}, {"Version", c_version}}; - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({wrongPrerequisite})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing Prerequisite.Namespace in response"); - } - - SECTION("Missing Prerequisite.Name") - { - const json wrongPrerequisite = {{"Namespace", c_ns}, {"Version", c_version}}; - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({wrongPrerequisite})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing Prerequisite.Name in response"); - } - - SECTION("Missing Prerequisite.Version") - { - const json wrongPrerequisite = {{"Namespace", c_ns}, {"Name", c_name}}; - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({wrongPrerequisite})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Missing Prerequisite.Version in response"); - } - } - - SECTION("Wrong types") - { - SECTION("UpdateId") - { - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", 1}, - {"Prerequisites", json::array({contentId})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "UpdateId is not a string"); - } - - SECTION("Prerequisites") - { - SECTION("Not an array") - { - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", contentId}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Prerequisites is not an array"); - } - - SECTION("Element is not an object") - { - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({1})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Prerequisite element is not a JSON object"); - } - - SECTION("Prerequisite.Namespace") - { - const json wrongPrerequisite = {{"Namespace", 1}, {"Name", c_name}, {"Version", c_version}}; - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({wrongPrerequisite})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Prerequisite.Namespace is not a string"); - } - - SECTION("Prerequisite.Name") - { - const json wrongPrerequisite = {{"Namespace", c_ns}, {"Name", 1}, {"Version", c_version}}; - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({wrongPrerequisite})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Prerequisite.Name is not a string"); - } - - SECTION("Prerequisite.Version") - { - const json wrongPrerequisite = {{"Namespace", c_ns}, {"Name", c_name}, {"Version", 1}}; - const json versionEntity = {{"ContentId", contentId}, - {"UpdateId", c_updateId}, - {"Prerequisites", json::array({wrongPrerequisite})}}; - REQUIRE_THROWS_CODE_MSG(VersionEntity::FromJson(versionEntity, handler), - ServiceInvalidResponse, - "Prerequisite.Version is not a string"); - } - } - } - } -} - -TEST("Testing VersionEntity conversions") -{ - ReportingHandler handler; - handler.SetLoggingCallback(LogCallbackToTest); - - auto CheckContentId = [&](const ContentId& contentId) { - REQUIRE(contentId.GetNameSpace() == c_ns); - REQUIRE(contentId.GetName() == c_name); - REQUIRE(contentId.GetVersion() == c_version); - }; - - SECTION("VersionEntity::ToContentId()") - { - SECTION("Success with GenericVersionEntity") - { - std::unique_ptr entity = std::make_unique(); - entity->contentId.nameSpace = c_ns; - entity->contentId.name = c_name; - entity->contentId.version = c_version; - - auto contentId = VersionEntity::ToContentId(std::move(*entity), handler); - CheckContentId(*contentId); - } - - SECTION("Success with AppVersionEntity") - { - std::unique_ptr entity = std::make_unique(); - auto appEntity = AppVersionEntity::GetAppVersionEntityPtr(entity, handler); - appEntity->contentId.nameSpace = c_ns; - appEntity->contentId.name = c_name; - appEntity->contentId.version = c_version; - appEntity->updateId = c_updateId; - - std::unique_ptr prereqEntity = std::make_unique(); - prereqEntity->contentId.nameSpace = c_ns; - prereqEntity->contentId.name = c_name; - prereqEntity->contentId.version = c_version; - - appEntity->prerequisites.push_back(std::move(*prereqEntity)); - - auto contentId = VersionEntity::ToContentId(std::move(*entity), handler); - CheckContentId(*contentId); - } - } -} diff --git a/src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.cpp b/src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.cpp deleted file mode 100644 index d1b5de29b1..0000000000 --- a/src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "SFSExceptionMatcher.h" - -#include - -using namespace SFS; -using namespace SFS::details; -using namespace SFS::test; - -SFSExceptionMatcher::SFSExceptionMatcher(const Result::Code& code) : m_code(code) -{ -} - -SFSExceptionMatcher::SFSExceptionMatcher(const Result::Code& code, std::string message) - : m_code(code) - , m_message(std::move(message)) -{ -} - -bool SFSExceptionMatcher::match(const SFSException& other) const -{ - if (m_message) - { - return other.GetResult() == m_code && other.what() == m_message; - } - return other.GetResult() == m_code; -} - -std::string SFSExceptionMatcher::describe() const -{ - return "Equals: " + std::string(ToString(m_code)) + (m_message ? " with message: " + *m_message : ""); -} - -template -SFSExceptionGenericMatcher::SFSExceptionGenericMatcher(const Result::Code& code, Matcher messageMatcher) - : m_code(code) - , m_messageMatcher(std::move(messageMatcher)) -{ -} - -template -bool SFSExceptionGenericMatcher::match(const SFSException& other) const -{ - return other.GetResult() == m_code && m_messageMatcher.match(other.what()); -} - -template -std::string SFSExceptionGenericMatcher::describe() const -{ - return "Equals: " + std::string(ToString(m_code)) + m_messageMatcher.describe(); -} - -template struct SFS::test::SFSExceptionGenericMatcher; -template struct SFS::test::SFSExceptionGenericMatcher; -template struct SFS::test::SFSExceptionGenericMatcher; -template struct SFS::test::SFSExceptionGenericMatcher; -template struct SFS::test::SFSExceptionGenericMatcher; diff --git a/src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.h b/src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.h deleted file mode 100644 index 89e17713dc..0000000000 --- a/src/SfsClient/sfs-client/client/tests/util/SFSExceptionMatcher.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "Result.h" -#include "SFSException.h" - -#include -#include - -#include -#include - -#define REQUIRE_THROWS_CODE(call, code) \ - REQUIRE_THROWS_MATCHES(call, SFS::details::SFSException, SFS::test::SFSExceptionMatcher(SFS::Result::code)) -#define REQUIRE_THROWS_CODE_MSG(call, code, message) \ - REQUIRE_THROWS_MATCHES(call, SFS::details::SFSException, SFS::test::SFSExceptionMatcher(SFS::Result::code, message)) -#define REQUIRE_THROWS_CODE_MSG_MATCHES(call, code, matcher) \ - REQUIRE_THROWS_MATCHES(call, \ - SFS::details::SFSException, \ - SFS::test::SFSExceptionGenericMatcher(SFS::Result::code, matcher)) - -namespace SFS::test -{ -struct SFSExceptionMatcher : Catch::Matchers::MatcherGenericBase -{ - SFSExceptionMatcher(const Result::Code& code); - SFSExceptionMatcher(const Result::Code& code, std::string message); - - bool match(const SFS::details::SFSException& other) const; - std::string describe() const override; - - private: - Result::Code m_code{Result::Success}; - std::optional m_message; -}; - -template -struct SFSExceptionGenericMatcher : Catch::Matchers::MatcherGenericBase -{ - SFSExceptionGenericMatcher(const Result::Code& code, Matcher messageMatcher); - - bool match(const SFS::details::SFSException& other) const; - std::string describe() const override; - - private: - Result::Code m_code{Result::Success}; - Matcher m_messageMatcher; -}; -} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/client/tests/util/TestHelper.cpp b/src/SfsClient/sfs-client/client/tests/util/TestHelper.cpp deleted file mode 100644 index d569796323..0000000000 --- a/src/SfsClient/sfs-client/client/tests/util/TestHelper.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "TestHelper.h" - -#include -#include -#include -#include -#include -#include - -#include - -static std::mutex s_logMutex; - -namespace -{ -std::string TimestampToString(std::chrono::time_point time) -{ - using namespace std::chrono; - - // get number of milliseconds for the current second - // (remainder after division into seconds) - auto ms = duration_cast(time.time_since_epoch()) % 1000; - - auto timer = system_clock::to_time_t(time); - - std::stringstream timeStream; - struct tm gmTime; -#ifdef _WIN32 - gmtime_s(&gmTime, &timer); // gmtime_s is the safe version of gmtime, not available on Linux -#else - gmTime = (*std::gmtime(&timer)); -#endif - timeStream << std::put_time(&gmTime, "%F %X"); // yyyy-mm-dd HH:MM:SS - timeStream << '.' << std::setfill('0') << std::setw(3) << ms.count(); - - return timeStream.str(); -} -} // namespace - -void SFS::test::LogCallbackToTest(const SFS::LogData& logData) -{ - std::lock_guard guard(s_logMutex); - UNSCOPED_INFO("Log: " << TimestampToString(logData.time) << " [" << ToString(logData.severity) << "]" - << " " << std::filesystem::path(logData.file).filename().string() << ":" << logData.line - << " " << logData.message); -} diff --git a/src/SfsClient/sfs-client/client/tests/util/TestHelper.h b/src/SfsClient/sfs-client/client/tests/util/TestHelper.h deleted file mode 100644 index 7fbaff0c16..0000000000 --- a/src/SfsClient/sfs-client/client/tests/util/TestHelper.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "sfsclient/Logging.h" - -#include - -#define TEST_UNSCOPED_INFO(message) \ - do \ - { \ - const std::string __message = (message); \ - SFS::test::LogCallbackToTest({SFS::LogSeverity::Info, \ - __message.c_str(), \ - __FILE__, \ - __LINE__, \ - __FUNCTION__, \ - std::chrono::system_clock::now()}); \ - } while ((void)0, 0) - -namespace SFS::test -{ -// Use this method to redirect the library logging to the Catch2 logging system -void LogCallbackToTest(const SFS::LogData& logData); -} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/cmake/SFSOptions.cmake b/src/SfsClient/sfs-client/cmake/SFSOptions.cmake deleted file mode 100644 index dde611961c..0000000000 --- a/src/SfsClient/sfs-client/cmake/SFSOptions.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# Define custom options for CMake. -# Options are passed to cmake through command line, -Doption=value -option(SFS_BUILD_TESTS "Indicates if tests should be built." OFF) - -option(SFS_BUILD_SAMPLES "Indicates if samples should be built." OFF) - -option( - SFS_ENABLE_TEST_OVERRIDES - "Set SFS_ENABLE_OVERRIDES to ON to enable certain test overrides through environment variables." - OFF) - -option( - SFS_WINDOWS_STATIC_ONLY - "Indicates if only static libraries and dependencies should be built on Windows." - OFF) diff --git a/src/SfsClient/sfs-client/cmake/sfsclient-config.cmake.in b/src/SfsClient/sfs-client/cmake/sfsclient-config.cmake.in deleted file mode 100644 index 8e4d754c70..0000000000 --- a/src/SfsClient/sfs-client/cmake/sfsclient-config.cmake.in +++ /dev/null @@ -1,16 +0,0 @@ -# Generates a macro to auto-configure everything -@PACKAGE_INIT@ - -set(SFS_BUILD_TESTS @SFS_BUILD_TESTS@) - -include(CMakeFindDependencyMacro) -find_dependency(CURL) -find_dependency(nlohmann_json) -find_dependency(correlation_vector) - -if(SFS_BUILD_TESTS) - find_dependency(Catch2) - find_dependency(cpp-httplib) -endif() - -include("${CMAKE_CURRENT_LIST_DIR}/sfsclient-targets.cmake") \ No newline at end of file diff --git a/src/SfsClient/sfs-client/pre-commit-wrapper.sh b/src/SfsClient/sfs-client/pre-commit-wrapper.sh deleted file mode 100644 index 53a0241413..0000000000 --- a/src/SfsClient/sfs-client/pre-commit-wrapper.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -# -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# -# A wrapper script to call the pre-commit.sh script -# -# Since git hooks are not committed to the server, existing only -# in the .git folder, this will be copied there and call the actual -# hook script only in case it exists. This should prevent issues -# with the user having to work on an earlier commit that does not -# have the hook or its dependencies installed. - -root_dir=$(git rev-parse --show-toplevel) -hook_script="$root_dir/pre-commit.sh" -if [[ -f $hook_script ]]; then - . "$hook_script" -fi diff --git a/src/SfsClient/sfs-client/pre-commit.sh b/src/SfsClient/sfs-client/pre-commit.sh deleted file mode 100644 index 481450eeb6..0000000000 --- a/src/SfsClient/sfs-client/pre-commit.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/bin/sh -# -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# -# A hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# To avoid having this script called, use "git commit -n" - -# Get files that need to be checked against clang-format into an array - -# To understand the regex format in bash, take a look at https://tldp.org/LDP/abs/html/x17129.html -# For example, this is how the regex (\.cpp|\.h)$ matches the example file client/include/SFSClient.h: -# (\.cpp|\.h): Match either .cpp or .h. The . character is escaped because it has a special meaning in regex. -# $: Match end of string -pattern_to_include_in_clang_format='(\.cpp|\.h)$' -pattern_to_include_in_cmake_format='CMakeLists.txt' - -# Get the names of staged files (--cached) that are not deleted (--diff-filter=d) -all_staged_files=$(git diff --cached --name-only --diff-filter=d) -mapfile -t filtered_files <<<"$all_staged_files" - -declare -A files_with_unstaged_changes - -clang_format_files=() -cmake_format_files=() - -# Only those that match a pattern will be analyzed -max_index=${#filtered_files[@]} -for ((i = 0; i < max_index; i++)); do - if [[ "${filtered_files[$i]}" =~ $pattern_to_include_in_clang_format || "${filtered_files[$i]}" =~ $pattern_to_include_in_cmake_format ]]; then - if [[ "${filtered_files[$i]}" =~ $pattern_to_include_in_clang_format ]]; then - clang_format_files+=("${filtered_files[$i]}") - elif [[ "${filtered_files[$i]}" =~ $pattern_to_include_in_cmake_format ]]; then - cmake_format_files+=("${filtered_files[$i]}") - fi - - # Collect files that will be analyzed but have unstaged changes, as we don't want to overwrite these changes - # `git diff` is empty if a file has no unstaged changes - if [[ -n "$(git diff "${filtered_files[$i]}")" ]]; then - files_with_unstaged_changes["${filtered_files[$i]}"]=1 - fi - fi -done - -# Early stop if no files to check -if [[ ${clang_format_files[*]} == "" && ${cmake_format_files[*]} == "" ]]; then - exit 0 -fi - -RED='\033[0;31m' -YELLOW='\033[0;33m' -NC='\033[0m' # No color - -# -# clang-format -# - -# Check that clang-format is installed -python_dir="$(python -c 'import os,sysconfig;print(sysconfig.get_path("scripts",f"{os.name}_user"))')" -clang_format="$python_dir/clang-format.exe" - -if [[ ! -x "$(command -v "$clang_format")" ]]; then - echo -e "${RED}There are staged changes that need to be checked by the formatter." - echo -e "${RED}clang-format toolset was not found. Re-run scripts\Setup.ps1 to install it.${NC}" - exit 1 -fi - -formatted_files=() -unformatted_files=() -for file in "${clang_format_files[@]}"; do - # Check if file will be modified so we can properly inform the user of modified files - # Use `clang-format -n` which does a dry run and outputs a non-empty response if there's something to format - # Pass contents through stdin to get only the staged changes. --assume-filename is used to find the closest .clang-format - # `git show :` outputs the staged version of a file - if [[ -n $(git show ":$file" | "$clang_format" -n --assume-filename "$file" 2>&1) ]]; then - - # If the file has unstaged changes, we can't format it - if [[ -v "files_with_unstaged_changes["$file"]" ]] ; then - unformatted_files+=("$file") - else - # Collect file that will be formatted and then actually format in-place - formatted_files+=("$file") - "$clang_format" -i "$file" - fi - fi -done - - -# Report modified files at end so we can run both formatters and report all changes at once - -# -# cmake-format -# -cmake_format="$python_dir/cmake-format.exe" - -if [[ ! -x "$(command -v "$cmake_format")" ]]; then - echo -e "${RED}There are staged changes that need to be checked by the formatter." - echo -e "${RED}cmake-format toolset was not found. Re-run scripts\Setup.ps1 to install it.${NC}" - exit 1 -fi - -tmp_formatted_name="tmp_formatted" - -for file in "${cmake_format_files[@]}"; do - # Check if file will be modified so we can properly inform the user of modified files - # First create a temporary file with only staged changes since cmake-format does not support stdin - git show ":$file" > $tmp_formatted_name - # Use `cmake-format --check` which does a dry run and returns 1 if there's something to format - if ! "$cmake_format" --check "$tmp_formatted_name" > /dev/null 2>&1; then - - # If the file has unstaged changes, we can't format it - if [[ -v "files_with_unstaged_changes["$file"]" ]] ; then - unformatted_files+=("$file") - else - # Collect file that will be formatted and then actually format in-place - formatted_files+=("$file") - "$cmake_format" -i "$file" - fi - fi - rm $tmp_formatted_name -done - -IFS=$'\n' sorted_formatted_files=($(sort <<<"${formatted_files[*]}")) -IFS=$'\n' sorted_unformatted_files=($(sort <<<"${unformatted_files[*]}")) -unset IFS - -exit_code=0 - -# If there were formatted files, we report the changes -if [[ ${sorted_formatted_files[*]} != "" ]]; then - echo -e "${YELLOW}There are files that have been automatically formatted. Review and stage the changes before committing:" - for file in "${sorted_formatted_files[@]}"; do - echo -e "M\t$file" - done - echo -e "${NC}" - exit_code=1 -fi - -# If there were files that could not be formatted, we report them -if [[ ${unformatted_files[*]} != "" ]]; then - echo -e "${RED}There are files that need to be formatted but have unstaged changes. Either stage the files or stash/discard the unstaged changes:" - for file in "${unformatted_files[@]}"; do - echo -e "M\t$file" - done - echo -e "${NC}" - exit_code=1 -fi - -exit $exit_code diff --git a/src/SfsClient/sfs-client/samples/CMakeLists.txt b/src/SfsClient/sfs-client/samples/CMakeLists.txt deleted file mode 100644 index 7527cd456e..0000000000 --- a/src/SfsClient/sfs-client/samples/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -add_subdirectory(integration-do-client) -add_subdirectory(tool) diff --git a/src/SfsClient/sfs-client/samples/README.md b/src/SfsClient/sfs-client/samples/README.md deleted file mode 100644 index 0fd444de13..0000000000 --- a/src/SfsClient/sfs-client/samples/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Samples - -This folder contains a few samples that show how to use the SFS Client library. - -## integration-do-client - -This sample shows how to use the SFSClient to retrieva metadata and URLs from the SFS Service and the [Delivery Optimization SDK](https://github.com/microsoft/do-client) to perform the download of the retrieved URLs. - -In Windows, the DO SDK uses COM APIs. In Linux, it will require the DO Agent, which is built from the same repository. diff --git a/src/SfsClient/sfs-client/samples/integration-do-client/CMakeLists.txt b/src/SfsClient/sfs-client/samples/integration-do-client/CMakeLists.txt deleted file mode 100644 index 1b8c5a4470..0000000000 --- a/src/SfsClient/sfs-client/samples/integration-do-client/CMakeLists.txt +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -project(IntegrationDOClient LANGUAGES CXX) - -# Settings for do-client library -set(DO_INCLUDE_SDK - ON - CACHE BOOL "" FORCE) -set(DO_BUILD_TESTS - OFF - CACHE BOOL "" FORCE) -if(NOT DEFINED CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE debug) -endif() - -if(MSVC) - set(DO_PATCH_COMMAND "") -else() - # Populate LSB_RELEASE_ID_SHORT with the linux distro if available - find_program(LSB_RELEASE_EXEC lsb_release) - execute_process( - COMMAND ${LSB_RELEASE_EXEC} -is - OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if(LSB_RELEASE_ID_SHORT STREQUAL "Ubuntu") - # 1. Run bootstrap script to install Linux dependencies - # 2. Build & install DO Agent, which acts as a service for the REST API - set(DO_PATCH_COMMAND - cd && sudo ./build/scripts/bootstrap.sh --install - build && python3 ./build/build.py --project agent --package-for deb - && cd /tmp/build-deliveryoptimization-agent/linux-debug && sudo - dpkg -i deliveryoptimization-agent_1.1.0_amd64.deb) - else() - message(FATAL_ERROR "Unsupported platform ${LSB_RELEASE_ID_SHORT}") - endif() -endif() - -# Include the capability of fetching external content -include(FetchContent) - -FetchContent_Declare( - do-client - GIT_REPOSITORY https://github.com/microsoft/do-client.git - GIT_TAG d71ade6f692dd8bc319ec3228c956517e9b29292 # v1.1.0 - PATCH_COMMAND "${DO_PATCH_COMMAND}") -FetchContent_MakeAvailable(do-client) - -add_executable(${PROJECT_NAME}) - -target_sources(${PROJECT_NAME} PRIVATE IntegrationDOClient.cpp) - -set(SFS_CLIENT_LIB_NAME "Microsoft::${CMAKE_PROJECT_NAME}") -add_dependencies(${PROJECT_NAME} ${SFS_CLIENT_LIB_NAME}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${SFS_CLIENT_LIB_NAME}) - -set(DO_SDK_LIB_NAME "Microsoft::deliveryoptimization") -add_dependencies(${PROJECT_NAME} ${DO_SDK_LIB_NAME}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${DO_SDK_LIB_NAME}) - -target_include_directories(${PROJECT_NAME} PUBLIC ../../client/include) diff --git a/src/SfsClient/sfs-client/samples/integration-do-client/IntegrationDOClient.cpp b/src/SfsClient/sfs-client/samples/integration-do-client/IntegrationDOClient.cpp deleted file mode 100644 index 7fb6ce66ac..0000000000 --- a/src/SfsClient/sfs-client/samples/integration-do-client/IntegrationDOClient.cpp +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -namespace -{ -void DisplayUsage() -{ - std::cout << "Usage: ExampleIntegrationDOClient --product [options]" << std::endl - << std::endl - << "Required:" << std::endl - << " --product \t\tName or GUID of the product to be retrieved" << std::endl - << std::endl - << "Options:" << std::endl - << " -h, --help\t\t\tDisplay this help message" << std::endl - << " --accountId \t\tAccount ID of the SFS service, used to identify the caller" << std::endl - << " --outDir \t\tLocation where download will be placed. Defaults to a tmp dir" << std::endl - << std::endl - << std::endl - << "Example:" << std::endl - << " ExampleIntegrationDOClient --product \"msedge-stable-win-x64\" --accountId msedge" << std::endl; -} - -void DisplayHelp() -{ - std::cout - << "ExampleIntegrationDOClient" << std::endl - << "Copyright (c) Microsoft Corporation. All rights reserved." << std::endl - << std::endl - << "Use to interact with the SFS service through the SFS Client library and download using the Delivery Optimization SDK." - << std::endl - << std::endl; - DisplayUsage(); -} - -const std::string c_boldRedStart = "\033[1;31m"; -const std::string c_cyanStart = "\033[0;36m"; -const std::string c_darkGreyStart = "\033[0;90m"; -const std::string c_colorEnd = "\033[0m"; - -void PrintError(std::string_view message) -{ - std::cout << c_boldRedStart << message << c_colorEnd << std::endl; -} - -void PrintLog(std::string_view message) -{ - std::cout << c_cyanStart << message << c_colorEnd << std::endl; -} - -struct Settings -{ - bool displayHelp{true}; - std::string product; - std::string accountId; - std::string outDir; -}; - -int ParseArguments(const std::vector& args, Settings& settings) -{ - settings = {}; - settings.displayHelp = args.size() == 1; - - const size_t argsSize = args.size(); - auto validateArg = - [&argsSize](const size_t index, const std::string& switchName, const std::string_view& argValue) -> bool { - if (argsSize <= index + 1) - { - PrintError("Missing argument of --" + switchName + "."); - return false; - } - if (!argValue.empty()) - { - PrintError("--" + switchName + " can only be specified once."); - return false; - } - return true; - }; - - for (size_t i = 1; i < args.size(); ++i) - { - if (args[i].compare("-h") == 0 || args[i].compare("--help") == 0) - { - settings.displayHelp = true; - } - else if (args[i].compare("--product") == 0) - { - if (!validateArg(i, "product", settings.product)) - { - return 1; - } - settings.product = args[++i]; - } - else if (args[i].compare("--accountId") == 0) - { - if (!validateArg(i, "accountId", settings.accountId)) - { - return 1; - } - settings.accountId = args[++i]; - } - else if (args[i].compare("--outDir") == 0) - { - if (!validateArg(i, "outDir", settings.outDir)) - { - return 1; - } - settings.outDir = args[++i]; - } - else - { - PrintError("Unknown option " + std::string(args[i]) + ".\n"); - return 1; - } - } - - if (settings.product.empty()) - { - return 1; - } - - return 0; -} - -void LogResult(const SFS::Result& result) -{ - std::cout << " Result code: " << ToString(result.GetCode()); - if (!result.GetMsg().empty()) - { - std::cout << ". Message: " << result.GetMsg(); - } - std::cout << std::endl; -} - -std::string TimestampToString(std::chrono::time_point time) -{ - using namespace std::chrono; - - // get number of milliseconds for the current second - // (remainder after division into seconds) - auto ms = duration_cast(time.time_since_epoch()) % 1000; - - auto timer = system_clock::to_time_t(time); - - std::stringstream timeStream; - struct tm gmTime; -#ifdef _WIN32 - gmtime_s(&gmTime, &timer); // gmtime_s is the safe version of gmtime, not available on Linux -#else - gmTime = (*std::gmtime(&timer)); -#endif - timeStream << std::put_time(&gmTime, "%F %X"); // yyyy-mm-dd HH:MM:SS - timeStream << '.' << std::setfill('0') << std::setw(3) << ms.count(); - - return timeStream.str(); -} - -void LoggingCallback(const SFS::LogData& logData) -{ - std::cout << c_darkGreyStart << "Log: " << TimestampToString(logData.time) << " [" << ToString(logData.severity) - << "]" - << " " << std::filesystem::path(logData.file).filename().string() << ":" << logData.line << " " - << logData.message << c_colorEnd << std::endl; -} - -std::filesystem::path GetOutDir(const std::string& outDir) -{ - if (outDir.empty()) - { - auto tmpDir = std::filesystem::temp_directory_path(); - auto outDir = tmpDir / "SFSDownload"; - std::filesystem::create_directories(outDir); - - // Set write permissions to the download dir since the download happens from a different process, but only in - // non-Windows -#ifndef _WIN32 - std::filesystem::permissions(outDir, - std::filesystem::perms::owner_all | std::filesystem::perms::group_all | - std::filesystem::perms::others_all, - std::filesystem::perm_options::add); -#endif - return outDir.string(); - } - return outDir; -} - -std::vector GetLatestDownloadInfo(const Settings& settings) -{ - // Initialize SFSClient - PrintLog("Initializing SFSClient with accountId: " + settings.accountId); - - SFS::ClientConfig config; - config.accountId = settings.accountId; - config.logCallbackFn = LoggingCallback; - - std::unique_ptr sfsClient; - auto result = SFS::SFSClient::Make(config, sfsClient); - if (!result) - { - PrintError("Failed to initialize SFSClient."); - LogResult(result); - return {}; - } - - // Perform operations using SFSClient - PrintLog("Getting latest download info for product: " + settings.product); - std::vector contents; - SFS::RequestParams params; - params.productRequests = {{settings.product, {}}}; - result = sfsClient->GetLatestDownloadInfo(params, contents); - if (!result) - { - PrintError("Failed to get latest download info."); - LogResult(result); - return {}; - } - - return contents; -} - -#ifdef _WIN32 -class CoInitializeWrapper -{ - public: - CoInitializeWrapper() - { - CoInitializeEx(nullptr, COINIT_MULTITHREADED); - } - - ~CoInitializeWrapper() - { - CoUninitialize(); - } -}; -#endif - -std::string ErrorCodeToHexString(std::error_code error) -{ - std::stringstream stream; - stream << "0x" << std::hex << error.value(); - return stream.str(); -} - -int Download(const SFS::Content& content, const std::string& baseOutDir) -{ - PrintLog("Found content: " + content.GetContentId().GetNameSpace() + "/" + content.GetContentId().GetName() + "/" + - content.GetContentId().GetVersion()); - - const auto outDir = GetOutDir(baseOutDir); - PrintLog("Downloading files to: " + outDir.string()); - - for (const auto& file : content.GetFiles()) - { - PrintLog("Downloading file " + file.GetFileId() + " from " + file.GetUrl()); - auto outFilePath = outDir / file.GetFileId(); - - if (std::filesystem::exists(outFilePath)) - { - // In a real implementation, one would check the file sha1 or sha256 hash to see if the file is the same as - // the current one - PrintLog("File already exists, cannot download again. Skipping."); - continue; - } - - // Download the file using Delivery Optimization SDK - std::unique_ptr download; - auto error = microsoft::deliveryoptimization::download::make(file.GetUrl(), outFilePath.string(), download); - if (error) - { - PrintError("Failed to create download object with error " + ErrorCodeToHexString(error)); - return 1; - } - - error = download->start_and_wait_until_completion(); - if (error) - { - PrintError("Failed to download file with error " + ErrorCodeToHexString(error)); - return 1; - } - - PrintLog("File successfully downloaded"); - } - - return 0; -} -} // namespace - -int main(int argc, char* argv[]) -{ - Settings settings; - if (ParseArguments(std::vector(argv, argv + argc), settings) != 0) - { - DisplayUsage(); - return 1; - } - - if (settings.displayHelp) - { - DisplayHelp(); - return 0; - } - - auto contents = GetLatestDownloadInfo(settings); - if (contents.size() != 1) - { - return 1; - } - - // In Windows the DO SDK uses COM, so we must call CoInitializeEx before using it -#ifdef _WIN32 - CoInitializeWrapper coInitWrapper; -#endif - - if (Download(contents[0], settings.outDir) != 0) - { - return 1; - } - - return 0; -} diff --git a/src/SfsClient/sfs-client/samples/tool/CMakeLists.txt b/src/SfsClient/sfs-client/samples/tool/CMakeLists.txt deleted file mode 100644 index 0b378cd89f..0000000000 --- a/src/SfsClient/sfs-client/samples/tool/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# JSON library -find_package(nlohmann_json CONFIG REQUIRED) - -project(SFSClientTool LANGUAGES CXX) - -add_executable(${PROJECT_NAME}) - -target_sources(${PROJECT_NAME} PRIVATE SFSClientTool.cpp) - -set(SFS_CLIENT_LIB_NAME "Microsoft::${CMAKE_PROJECT_NAME}") -add_dependencies(${PROJECT_NAME} ${SFS_CLIENT_LIB_NAME}) - -target_link_libraries(${PROJECT_NAME} PRIVATE ${SFS_CLIENT_LIB_NAME}) -target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json) - -target_include_directories(${PROJECT_NAME} PUBLIC ../../client/include) - -set_compile_options_for_target(${PROJECT_NAME}) diff --git a/src/SfsClient/sfs-client/samples/tool/SFSClientTool.cpp b/src/SfsClient/sfs-client/samples/tool/SFSClientTool.cpp deleted file mode 100644 index b784c94611..0000000000 --- a/src/SfsClient/sfs-client/samples/tool/SFSClientTool.cpp +++ /dev/null @@ -1,548 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace SFS; -using json = nlohmann::json; - -namespace -{ -void DisplayUsage() -{ - std::cout - << "Usage: SFSClientTool --product --accountId [options]" << std::endl - << std::endl - << "Required:" << std::endl - << " --product \tName or GUID of the product to be retrieved" << std::endl - << " --accountId \t\tAccount ID of the SFS service, used to identify the caller" << std::endl - << std::endl - << "Options:" << std::endl - << " -h, --help\t\t\tDisplay this help message" << std::endl - << " -v, --version\t\t\tDisplay the library version" << std::endl - << " -o, --outputFile \t\tWhen specified, the JSON output is saved to this file" << std::endl - << " --isApp\t\t\tIndicates the specific product is an App" << std::endl - << " --instanceId \t\tA custom SFS instance ID" << std::endl - << " --namespace \t\tA custom SFS namespace" << std::endl - << " --customUrl \t\tA custom URL for the SFS service. Library must have been built with SFS_ENABLE_OVERRIDES" - << std::endl - << std::endl - << "Example:" << std::endl - << " SFSClientTool --product msedge-stable-win-x64 --accountId msedge" << std::endl; -} - -void DisplayHelp() -{ - std::cout << "SFSClient Tool" << std::endl - << "Copyright (c) Microsoft Corporation. All rights reserved." << std::endl - << std::endl - << "Use to interact with the SFS service through the SFS Client library." << std::endl - << std::endl; - DisplayUsage(); -} - -void DisplayVersion() -{ - std::cout << "SFSClient Tool " << SFSClient::GetVersion() << std::endl; -} - -const std::string c_boldRedStart = "\033[1;31m"; -const std::string c_cyanStart = "\033[0;36m"; -const std::string c_darkGreyStart = "\033[0;90m"; -const std::string c_colorEnd = "\033[0m"; - -void PrintError(std::string_view message) -{ - std::cout << c_boldRedStart << message << c_colorEnd << std::endl; -} - -void PrintLog(std::string_view message) -{ - std::cout << c_cyanStart << message << c_colorEnd << std::endl; -} - -bool AreEqualI(std::string_view a, std::string_view b) -{ - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { - return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); - }); -} - -struct Settings -{ - bool displayHelp{true}; - bool displayVersion{false}; - bool isApp{false}; - std::string product; - std::string accountId; - std::string instanceId; - std::string nameSpace; - std::string customUrl; - std::string outputFile; - - bool ShouldOutputToFile() const - { - return !outputFile.empty(); - } -}; - -void ParseArguments(const std::vector& args, Settings& settings) -{ - settings = {}; - settings.displayHelp = args.size() == 1; - - const size_t argsSize = args.size(); - auto validateArg = - [&argsSize](const size_t index, const std::string& switchName, const std::string_view& argValue) -> void { - if (argsSize <= index + 1) - { - throw std::runtime_error("Missing argument of --" + switchName); - } - if (!argValue.empty()) - { - throw std::runtime_error("--" + switchName + " can only be specified once"); - } - }; - - auto matchArg = - [](std::string_view arg, const std::string& shortSwitchName, const std::string& longSwitchName) -> bool { - return (!shortSwitchName.empty() && AreEqualI(arg, shortSwitchName)) || AreEqualI(arg, longSwitchName); - }; - - auto matchLongArg = [](std::string_view arg, const std::string& longSwitchName) -> bool { - return AreEqualI(arg, longSwitchName); - }; - - for (size_t i = 1; i < args.size(); ++i) - { - if (matchArg(args[i], "-h", "--help")) - { - settings.displayHelp = true; - } - else if (matchArg(args[i], "-v", "--version")) - { - settings.displayVersion = true; - } - else if (matchArg(args[i], "-o", "--outputFile")) - { - validateArg(i, "outputFile", settings.outputFile); - settings.outputFile = args[++i]; - } - else if (matchLongArg(args[i], "--isApp")) - { - settings.isApp = true; - } - else if (matchLongArg(args[i], "--product")) - { - validateArg(i, "product", settings.product); - settings.product = args[++i]; - } - else if (matchLongArg(args[i], "--accountId")) - { - validateArg(i, "accountId", settings.accountId); - settings.accountId = args[++i]; - } - else if (matchLongArg(args[i], "--instanceId")) - { - validateArg(i, "instanceId", settings.instanceId); - settings.instanceId = args[++i]; - } - else if (matchLongArg(args[i], "--namespace")) - { - validateArg(i, "namespace", settings.nameSpace); - settings.nameSpace = args[++i]; - } - else if (matchLongArg(args[i], "--customUrl")) - { - validateArg(i, "customUrl", settings.customUrl); - settings.customUrl = args[++i]; - } - else - { - throw std::runtime_error("Unknown option " + std::string(args[i])); - } - } - - if (!settings.displayHelp && (settings.product.empty() || settings.accountId.empty())) - { - throw std::runtime_error("--product and --accountId are required and cannot be empty"); - } -} - -constexpr std::string_view ToString(HashType type) -{ - switch (type) - { - case HashType::Sha1: - return "Sha1"; - case HashType::Sha256: - return "Sha256"; - } - return ""; -} - -constexpr std::string_view ToString(Architecture type) -{ - switch (type) - { - case Architecture::None: - return "None"; - case Architecture::Amd64: - return "amd64"; - case Architecture::Arm: - return "arm"; - case Architecture::Arm64: - return "arm64"; - case Architecture::x86: - return "x86"; - } - return ""; -} - -json ContentsToJson(const std::vector& contents) -{ - json out = json::array(); - for (const auto& content : contents) - { - json j = json::object(); - j["ContentId"]["Namespace"] = content.GetContentId().GetNameSpace(); - j["ContentId"]["Name"] = content.GetContentId().GetName(); - j["ContentId"]["Version"] = content.GetContentId().GetVersion(); - - j["Files"] = json::array(); - for (const auto& file : content.GetFiles()) - { - json fileJson = json::object(); - fileJson["FileId"] = file.GetFileId(); - fileJson["Url"] = file.GetUrl(); - fileJson["SizeInBytes"] = file.GetSizeInBytes(); - json hashes = json::object(); - for (const auto& hash : file.GetHashes()) - { - hashes[ToString(hash.first)] = hash.second; - } - fileJson["Hashes"] = hashes; - j["Files"].push_back(fileJson); - } - out.push_back(j); - } - - return out; -} - -json AppFileToJson(const AppFile& file) -{ - json fileJson = json::object(); - fileJson["FileId"] = file.GetFileId(); - fileJson["Url"] = file.GetUrl(); - fileJson["SizeInBytes"] = file.GetSizeInBytes(); - fileJson["FileMoniker"] = file.GetFileMoniker(); - json hashes = json::object(); - for (const auto& hash : file.GetHashes()) - { - hashes[ToString(hash.first)] = hash.second; - } - fileJson["Hashes"] = hashes; - - fileJson["ApplicabilityDetails"] = json::object(); - fileJson["ApplicabilityDetails"]["Architectures"] = json::array(); - for (const auto& arch : file.GetApplicabilityDetails().GetArchitectures()) - { - fileJson["ApplicabilityDetails"]["Architectures"].push_back(ToString(arch)); - } - fileJson["ApplicabilityDetails"]["PlatformApplicabilityForPackage"] = json::array(); - for (const auto& app : file.GetApplicabilityDetails().GetPlatformApplicabilityForPackage()) - { - fileJson["ApplicabilityDetails"]["PlatformApplicabilityForPackage"].push_back(app); - } - - return fileJson; -} - -json AppContentsToJson(const std::vector& contents) -{ - json out = json::array(); - for (const auto& content : contents) - { - json j = json::object(); - j["ContentId"]["Namespace"] = content.GetContentId().GetNameSpace(); - j["ContentId"]["Name"] = content.GetContentId().GetName(); - j["ContentId"]["Version"] = content.GetContentId().GetVersion(); - j["UpdateId"] = content.GetUpdateId(); - - j["Files"] = json::array(); - for (const auto& file : content.GetFiles()) - { - j["Files"].push_back(AppFileToJson(file)); - } - - j["Prerequisites"] = json::array(); - for (const auto& prereq : content.GetPrerequisites()) - { - json prereqJson = json::object(); - prereqJson["ContentId"]["Namespace"] = prereq.GetContentId().GetNameSpace(); - prereqJson["ContentId"]["Name"] = prereq.GetContentId().GetName(); - prereqJson["ContentId"]["Version"] = prereq.GetContentId().GetVersion(); - - prereqJson["Files"] = json::array(); - for (const auto& file : prereq.GetFiles()) - { - prereqJson["Files"].push_back(AppFileToJson(file)); - } - - j["Prerequisites"].push_back(prereqJson); - } - out.push_back(j); - } - - return out; -} - -void DisplayResults(const json& results) -{ - if (results.empty()) - { - return; - } - - PrintLog("Content found:"); - PrintLog(results.dump(2 /*indent*/)); -} - -Result JSONToFile(const json& results, const std::string& filename) -{ - if (results.empty()) - { - return Result::Unexpected; - } - - const std::filesystem::path filepath = std::filesystem::absolute(filename); - try - { - std::filesystem::create_directories(filepath.parent_path()); - } - catch (const std::exception& e) - { - PrintError("Failed to create parent directories for filepath: " + filepath.string() + ". Error: " + e.what()); - return Result::Unexpected; - } - - std::ofstream file(filepath, std::ios::out | std::ios::trunc); - if (!file.is_open()) - { - PrintError("Failed to open file for writing: " + filename); - return Result::Unexpected; - } - - file << results.dump(2 /*indent*/); - - PrintLog("Content found. Saved to file " + filepath.string()); - - return Result::Success; -} - -void LogResult(const SFS::Result& result) -{ - std::cout << " Result code: " << ToString(result.GetCode()); - if (!result.GetMsg().empty()) - { - std::cout << ". Message: " << result.GetMsg(); - } - std::cout << std::endl; -} - -std::string TimestampToString(std::chrono::time_point time) -{ - using namespace std::chrono; - - // get number of milliseconds for the current second - // (remainder after division into seconds) - auto ms = duration_cast(time.time_since_epoch()) % 1000; - - auto timer = system_clock::to_time_t(time); - - std::stringstream timeStream; - struct tm gmTime; -#ifdef _WIN32 - gmtime_s(&gmTime, &timer); // gmtime_s is the safe version of gmtime, not available on Linux -#else - gmTime = (*std::gmtime(&timer)); -#endif - timeStream << std::put_time(&gmTime, "%F %X"); // yyyy-mm-dd HH:MM:SS - timeStream << '.' << std::setfill('0') << std::setw(3) << ms.count(); - - return timeStream.str(); -} - -void LoggingCallback(const SFS::LogData& logData) -{ - std::cout << c_darkGreyStart << "Log: " << TimestampToString(logData.time) << " [" << ToString(logData.severity) - << "]" - << " " << std::filesystem::path(logData.file).filename().string() << ":" << logData.line << " " - << logData.message << c_colorEnd << std::endl; -} - -bool SetEnv(const std::string& varName, const std::string& value) -{ - if (varName.empty() || value.empty()) - { - return false; - } -#ifdef _WIN32 - return _putenv_s(varName.c_str(), value.c_str()) == 0; -#else - return setenv(varName.c_str(), value.c_str(), 1 /*overwrite*/) == 0; -#endif -} - -Result GetLatestDownloadInfo(const SFSClient& sfsClient, const Settings& settings, json& out) -{ - PrintLog("Getting latest download info for product: " + settings.product); - RequestParams params; - params.productRequests = {{settings.product, {}}}; - - if (settings.isApp) - { - std::vector appContents; - auto result = sfsClient.GetLatestAppDownloadInfo(params, appContents); - if (!result) - { - PrintError("Failed to get latest download info for app."); - LogResult(result); - return result.GetCode(); - } - - out = AppContentsToJson(appContents); - } - else - { - std::vector contents; - auto result = sfsClient.GetLatestDownloadInfo(params, contents); - if (!result) - { - PrintError("Failed to get latest download info."); - LogResult(result); - return result.GetCode(); - } - - out = ContentsToJson(contents); - } - - if (out.empty()) - { - PrintError("No results found."); - return Result::Unexpected; - } - - return Result::Success; -} - -Result HandleJSONContents(const Settings& settings, const json& contents) -{ - if (settings.ShouldOutputToFile()) - { - auto result = JSONToFile(contents, settings.outputFile); - if (!result) - { - PrintError("Failed to save to file."); - return result.GetCode(); - } - } - else - { - DisplayResults(contents); - return Result::Success; - } - - return Result::Success; -} -} // namespace - -int main(int argc, char* argv[]) -{ - Settings settings; - try - { - ParseArguments(std::vector(argv, argv + argc), settings); - } - catch (const std::runtime_error& e) - { - PrintError(std::string(e.what()) + "\n"); - DisplayUsage(); - return 1; - } - - if (settings.displayVersion) - { - DisplayVersion(); - return 0; - } - - if (settings.displayHelp) - { - DisplayHelp(); - return 0; - } - - if (!settings.customUrl.empty()) - { - SetEnv("SFS_TEST_OVERRIDE_BASE_URL", settings.customUrl); - PrintLog("Using custom URL: " + settings.customUrl); - PrintLog("Note that the library must have been built with SFS_ENABLE_OVERRIDES to use a custom URL."); - } - - // Initialize SFSClient - PrintLog("Initializing SFSClient with accountId: " + settings.accountId + - (settings.instanceId.empty() ? "" : ", instanceId: " + settings.instanceId) + - (settings.nameSpace.empty() ? "" : ", namespace: " + settings.nameSpace)); - - ClientConfig config; - config.accountId = settings.accountId; - if (!settings.instanceId.empty()) - { - config.instanceId = settings.instanceId; - } - if (!settings.nameSpace.empty()) - { - config.nameSpace = settings.nameSpace; - } - config.logCallbackFn = LoggingCallback; - - std::unique_ptr sfsClient; - auto result = SFSClient::Make(config, sfsClient); - if (!result) - { - PrintError("Failed to initialize SFSClient."); - LogResult(result); - return result.GetCode(); - } - - // Perform operations using SFSClient - json contents; - result = GetLatestDownloadInfo(*sfsClient, settings, contents); - if (!result) - { - LogResult(result); - return result.GetCode(); - } - - result = HandleJSONContents(settings, contents); - if (!result) - { - LogResult(result); - return result.GetCode(); - } - - return 0; -} diff --git a/src/SfsClient/sfs-client/scripts/Build.ps1 b/src/SfsClient/sfs-client/scripts/Build.ps1 deleted file mode 100644 index c32dda1640..0000000000 --- a/src/SfsClient/sfs-client/scripts/Build.ps1 +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -<# -.SYNOPSIS -Simplifies build commands for the SFS Client. - -.PARAMETER Clean -Use this to clean the build folder before building. - -.PARAMETER BuildType -Use this to define the build type between "Debug" and "Release". The default is "Debug". - -.PARAMETER EnableTestOverrides -Use this to enable test overrides. - -.PARAMETER BuildTests -Use this to enable building tests. On by default. - -.PARAMETER BuildSamples -Use this to enable building samples. On by default. - -.DESCRIPTION -This script will contain the build commands for the SFS Client. The default build folder will be "/build". -Use this on Windows platforms in a PowerShell session. - -.EXAMPLE -PS> ./scripts/Build.ps1 -#> -param ( - [switch] $Clean = $false, - # BuildType is a build time parameter for Visual Studio generator in CMake - [ValidateSet("Debug", "Release")] - [string] $BuildType = "Debug", - # Make sure when adding a new switch below to check if it requires CMake regeneration - [switch] $EnableTestOverrides = $false, - [bool] $BuildTests = $true, - [bool] $BuildSamples = $true -) - -$GitRoot = (Resolve-Path (&git -C $PSScriptRoot rev-parse --show-toplevel)).Path -if (!(Test-Path "$GitRoot\vcpkg")) -{ - throw "vcpkg not found at $GitRoot\vcpkg. Run the Setup.ps1 script first." -} - -$BuildFolder = "$GitRoot/build" -if ($Clean -and (Test-Path $BuildFolder)) -{ - Write-Host -ForegroundColor Yellow "Cleaning build folder before build..." - Remove-Item -Recurse -Force $BuildFolder -} - -$Regenerate = $false -$CMakeCacheFile = "$BuildFolder\CMakeCache.txt" -$EnableTestOverridesStr = if ($EnableTestOverrides) {"ON"} else {"OFF"} -$BuildTestsOverridesStr = if ($BuildTests) {"ON"} else {"OFF"} -$BuildSamplesOverridesStr = if ($BuildSamples) {"ON"} else {"OFF"} - -function Test-CMakeCacheValueNoMatch($CMakeCacheFile, $Pattern, $ExpectedValue) -{ - $Match = Select-String -Path $CMakeCacheFile -Pattern $Pattern - if ($null -ne $Match -and $null -ne $Match.Matches.Groups[1]) - { - return $ExpectedValue -ne $Match.Matches.Groups[1].Value - } - return $true -} - -if (Test-Path $CMakeCacheFile) -{ - # Regenerate if one of the build options is set to a different value than the one passed in - $Regenerate = Test-CMakeCacheValueNoMatch $CMakeCacheFile "^SFS_ENABLE_TEST_OVERRIDES:BOOL=(.*)$" $EnableTestOverridesStr - $Regenerate = Test-CMakeCacheValueNoMatch $CMakeCacheFile "^SFS_BUILD_TESTS:BOOL=(.*)$" $BuildTestsOverridesStr - $Regenerate = Test-CMakeCacheValueNoMatch $CMakeCacheFile "^SFS_BUILD_SAMPLES:BOOL=(.*)$" $BuildSamplesOverridesStr -} - -# Configure cmake if build folder doesn't exist or if the build must be regenerated. -# This creates build targets that will be used by the build command -if (!(Test-Path $BuildFolder) -or $Regenerate) -{ - $Options = "-DSFS_ENABLE_TEST_OVERRIDES=$EnableTestOverridesStr"; - $Options += " -DSFS_BUILD_TESTS=$BuildTestsOverridesStr"; - $Options += " -DSFS_BUILD_SAMPLES=$BuildSamplesOverridesStr"; - $Options += " -DSFS_WINDOWS_STATIC_ONLY=ON"; - Invoke-Expression "cmake -S $GitRoot -B $BuildFolder $Options" -} - -# This is the build command. If any CMakeLists.txt files change, this will also reconfigure before building -cmake --build $BuildFolder --config $BuildType diff --git a/src/SfsClient/sfs-client/scripts/Setup.ps1 b/src/SfsClient/sfs-client/scripts/Setup.ps1 deleted file mode 100644 index 3a5b0623aa..0000000000 --- a/src/SfsClient/sfs-client/scripts/Setup.ps1 +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -<# -.SYNOPSIS -Sets up dependencies required to build and work with the SFS Client. - -.DESCRIPTION -This script will install all of the dependencies required to build and work with the SFS Client. -Use this on Windows platforms in a PowerShell session. - -.EXAMPLE -PS> .\scripts\Setup.ps1 -#> - -$ErrorActionPreference = "Stop" - -$GitRoot = (Resolve-Path (&git -C $PSScriptRoot rev-parse --show-toplevel)).Path - -function Update-Env { - $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") -} - -function Install-Python { - Write-Host -ForegroundColor Cyan "Installing latest Python if it's not installed" - - # We use the "python --version" command to check if it exists because by default Windows now comes - # with a python.exe from the Store that simply opens the Store when someone tries to use python.exe - python --version 2>&1 | Out-Null - if (!$?) { - winget install python - if (!$?) { - Write-Host -ForegroundColor Red "Failed to install Python" - exit 1 - } - } -} - -function Install-PipDependencies { - Write-Host -ForegroundColor Cyan "`nInstalling dependencies using pip" - - # Upgrade pip and install requirements. Filter out output for dependencies that are already installed - $PipInstalledPackageString = "Requirement already satisfied" - python -m pip install --upgrade pip | Select-String -Pattern $PipInstalledPackageString -NotMatch - - # Installing to user site packages - $PipReqs = Join-Path $PSScriptRoot "pip.requirements.txt" -Resolve - pip install --user -r $PipReqs | Select-String -Pattern $PipInstalledPackageString -NotMatch -} - -function Install-CMake { - Write-Host -ForegroundColor Cyan "`nInstalling cmake if it's not installed" - - # Installing cmake from winget because it adds to PATH - try { - cmake --version - } - catch { - winget install cmake - if (!$?) { - Write-Host -ForegroundColor Red "Failed to install cmake" - exit 1 - } - - # Update env with newly installed cmake - Update-Env - } -} - -function Install-CppBuildTools { - Write-Host -ForegroundColor Cyan "`nInstalling C++ Builds tools if they are not installed" - - # Instaling vswhere, which will be used to query for the required build tools - try { - vswhere -? 2>&1 | Out-Null - } - catch { - winget install vswhere - if (!$?) { - Write-Host -ForegroundColor Red "Failed to install vswhere" - exit 1 - } - } - - # - Microsoft.VisualStudio.Workload.VCTools is the C++ workload in the Visual Studio Build Tools - # - Microsoft.VisualStudio.Workload.NativeDesktop is the C++ workload that comes pre-installed in the github runner image - $ExistingBuildTools = vswhere -products * -requires Microsoft.VisualStudio.Workload.VCTools Microsoft.VisualStudio.Workload.NativeDesktop -requiresAny -format json | ConvertFrom-Json - if ($null -eq $ExistingBuildTools) - { - Write-Host "`nTools not found, installing..." - - # --wait makes the install synchronous - winget install Microsoft.VisualStudio.2022.BuildTools --silent --override "--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --remove Microsoft.VisualStudio.Component.VC.CMake.Project" - if (!$?) { - Write-Host -ForegroundColor Red "Failed to install build tools" - exit 1 - } - } -} - -function Install-Vcpkg { - Write-Host -ForegroundColor Cyan "`nSetting up vcpkg" - - if (!(Test-Path vcpkg)) { - Write-Host "Cloning vcpkg repo" - git clone https://github.com/microsoft/vcpkg $GitRoot\vcpkg - } - else { - Write-Host "Checking if vcpkg repo has new commits" - $NoUpdatesString = "Already up to date." - git -C "$GitRoot\vcpkg" pull --show-forced-updates | Select-String -Pattern $NoUpdatesString -NotMatch - } - - # Bootstrapping on every setup updates the vcpkg.exe and solves potential issues with VS Build Tools not being found - & "$GitRoot\vcpkg\bootstrap-vcpkg.bat" -} - -function Set-GitHooks { - Write-Host -ForegroundColor Cyan "`nSetting Git hooks" - - $HookDestDir = Join-Path $GitRoot "\.git\hooks" -Resolve - $GitHooks = @{"pre-commit-wrapper.sh" = "pre-commit" } - foreach ($i in $GitHooks.GetEnumerator()) { - $HookSrc = Join-Path $GitRoot $i.Name -Resolve - $HookDest = Join-Path $HookDestDir $i.Value - - # If the destination doesn't exist or is different than the one in the source, we'll copy it over. - if (-not (Test-Path $HookDest) -or (Get-FileHash $HookDest).Hash -ne (Get-FileHash $HookSrc).Hash) { - Copy-Item -Path $HookSrc -Destination $HookDest -Force | Out-Null - Write-Host -ForegroundColor Cyan "Setup git $($i.Value) hook with $HookSrc" - } - } -} - -function Set-Aliases { - Write-Host -ForegroundColor Cyan "`nSetting aliases" - - $PythonScriptsDir = python -c "import os,sysconfig;print(sysconfig.get_path('scripts',f'{os.name}_user'))" - - $Aliases = [ordered]@{ - "build" = "$GitRoot\scripts\Build.ps1" - "clang-format" = "$PythonScriptsDir\clang-format.exe" - "cmake-format" = "$PythonScriptsDir\cmake-format.exe" - "test" = "$GitRoot\scripts\Test.ps1" - "vcpkg" = "$GitRoot\vcpkg\vcpkg.exe" - } - foreach ($i in $Aliases.GetEnumerator()) { - $Alias = $i.Name - $Target = $i.Value - - # If the alias doesn't exist or is set to something different than the one we want to target, we'll add it. - $AliasDoesNotExist = !(Get-Alias $Alias -ErrorAction SilentlyContinue) - if ($AliasDoesNotExist -or (Get-Alias $Alias).Definition -ne $Target) { - if (!$AliasDoesNotExist) { - Remove-Alias $Alias -Scope Global - } - New-Alias -Name $Alias -Value $Target -Scope Global - Write-Host "Setup alias $Alias" - } - } -} - -Install-Python -Install-PipDependencies -Install-CMake -Install-CppBuildTools -Install-Vcpkg -Set-GitHooks -Set-Aliases diff --git a/src/SfsClient/sfs-client/scripts/Test.ps1 b/src/SfsClient/sfs-client/scripts/Test.ps1 deleted file mode 100644 index 3e659bf5ef..0000000000 --- a/src/SfsClient/sfs-client/scripts/Test.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -<# -.SYNOPSIS -Simplifies test commands for the SFS Client. - -.DESCRIPTION -This script will contain the test commands for the SFS Client. -Use this on Windows platforms in a PowerShell session. - -.EXAMPLE -PS> ./scripts/Test.ps1 -#> -param ( - [switch] $OutputOnFailure = $false -) - -$GitRoot = (Resolve-Path (&git -C $PSScriptRoot rev-parse --show-toplevel)).Path -$BuildFolder = "$GitRoot/build" - -$cmd = "ctest --test-dir ""$BuildFolder/client""" -if ($OutputOnFailure) -{ - $cmd += " --output-on-failure" -} - -Invoke-Expression $cmd diff --git a/src/SfsClient/sfs-client/scripts/build.sh b/src/SfsClient/sfs-client/scripts/build.sh deleted file mode 100755 index 2a72fc5fd4..0000000000 --- a/src/SfsClient/sfs-client/scripts/build.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/bin/bash - -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# Synopsis: Simplifies build commands for the SFS Client. -# -# Description: This script will contain the build commands for the SFS Client. The default build folder will be "/build". -# Use this on non-Windows platforms in a bash session. -# -# Example: -# $ ./scripts/build.sh -# - -# Ensures script stops on errors -set -e - -if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - error "Script is being sourced, it should be executed instead." - return 1 -fi - -COLOR_RED="\033[1;31m" -COLOR_YELLOW="\033[1;33m" -BOLD_DEFAULT_COLOR="\033[1m" -NO_COLOR="\033[0m" - -error() { echo -e "${COLOR_RED}$*${NO_COLOR}" >&2; exit 1; } -warn() { echo -e "${COLOR_YELLOW}$*${NO_COLOR}"; } - -clean=false -enable_test_overrides="OFF" -build_tests="ON" -build_samples="ON" -build_type="Debug" - -usage() { echo -e "Usage: $0 [-c|--clean] [-b|--build-type {Debug,Release}] [-t|--enable-test-overrides] - [--build-tests {${BOLD_DEFAULT_COLOR}ON${NO_COLOR}, OFF}] [--build-samples {${BOLD_DEFAULT_COLOR}ON${NO_COLOR}, OFF}]" 1>&2; exit 1; } - -# Make sure when adding a new option to check if it requires CMake regeneration - -if ! opts=$(getopt \ - --longoptions "clean,build-type:,enable-test-overrides,build-tests:,build-samples:" \ - --name "$(basename "$0")" \ - --options "cb:t" \ - -- "$@" -); then - usage -fi - -eval set "--$opts" - -while [ $# -gt 0 ]; do - case "$1" in - -c|--clean) - clean=true - shift 1 - ;; - -b|--build-type) - shift 1 - build_type=$1 - case "$build_type" in - Debug|Release) - ;; - *) - usage - ;; - esac - shift 1 - ;; - -t|--enable-test-overrides) - enable_test_overrides="ON" - shift 1 - ;; - --build-tests) - shift 1 - build_tests=$1 - case "$build_tests" in - ON|OFF) - ;; - *) - usage - ;; - esac - shift 1 - ;; - --build-samples) - shift 1 - build_samples=$1 - case "$build_samples" in - ON|OFF) - ;; - *) - usage - ;; - esac - shift 1 - ;; - *) - break - ;; - esac -done - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)" -git_root=$(git -C "$script_dir" rev-parse --show-toplevel) - -vcpkg_dir="$git_root/vcpkg" -if [ ! -d "$vcpkg_dir" ]; then - error "vcpkg not found at $git_root/vcpkg. Source the setup.sh script first." -fi - -build_folder="$git_root/build" -if $clean && [ -d "$build_folder" ]; then - warn "Cleaning build folder before build..." - rm -r -f "$build_folder" -fi - -regenerate=false -cmake_cache_file="$build_folder/CMakeCache.txt" - -test_cmake_cache_value_no_match() { - local cmake_cache_file=$1 - local pattern=$2 - local expected_value=$3 - - value=$(sed -nr "s/$pattern/\1/p" "$cmake_cache_file") - if [ -n "$value" ] && [ "$value" == "$expected_value" ]; then - return 1 - fi - return 0 -} - -if [ -f "$cmake_cache_file" ]; then - # Regenerate if one of the build options is set to a different value than the one passed in - if test_cmake_cache_value_no_match "$cmake_cache_file" "^CMAKE_BUILD_TYPE:STRING=(.*)$" "$build_type"; then - regenerate=true - fi - if test_cmake_cache_value_no_match "$cmake_cache_file" "^SFS_ENABLE_TEST_OVERRIDES:BOOL=(.*)$" "$enable_test_overrides"; then - regenerate=true - fi - if test_cmake_cache_value_no_match "$cmake_cache_file" "^SFS_BUILD_TESTS:BOOL=(.*)$" "$build_tests"; then - regenerate=true - fi - if test_cmake_cache_value_no_match "$cmake_cache_file" "^SFS_BUILD_SAMPLES:BOOL=(.*)$" "$build_samples"; then - regenerate=true - fi -fi - -# Configure cmake if build folder doesn't exist or if the build must be regenerated. -# This creates build targets that will be used by the build command -if [ ! -d "$build_folder" ] || $regenerate ; then - cmake \ - -S "$git_root" \ - -B "$build_folder" \ - -DCMAKE_BUILD_TYPE="$build_type" \ - -DSFS_ENABLE_TEST_OVERRIDES="$enable_test_overrides" \ - -DSFS_BUILD_TESTS="$build_tests" \ - -DSFS_BUILD_SAMPLES="$build_samples" -fi - -# This is the build command. If any CMakeLists.txt files change, this will also reconfigure before building -cmake --build "$build_folder" diff --git a/src/SfsClient/sfs-client/scripts/check-format.py b/src/SfsClient/sfs-client/scripts/check-format.py deleted file mode 100644 index 416b852055..0000000000 --- a/src/SfsClient/sfs-client/scripts/check-format.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -"""Checks the formatting of the codebase and fails if it is not correct.""" - -import os -import subprocess -import sys -import sysconfig - -script_dir = os.path.dirname(os.path.realpath(__file__)) -root_dir = os.path.join(script_dir, "..") -os.chdir(root_dir) - -user_dir = sysconfig.get_path("scripts",f"{os.name}_user") -if os.name == 'nt': - clang_format = "{}\clang-format.exe".format(user_dir) - cmake_format = "{}\cmake-format.exe".format(user_dir) -else: - clang_format = "{}/clang-format".format(user_dir) - cmake_format = "{}/cmake-format".format(user_dir) - -if not os.path.exists(clang_format): - print("clang-format not found at: {}".format(clang_format)) - sys.exit(1) - -if not os.path.exists(cmake_format): - print("cmake-format not found at: {}".format(cmake_format)) - sys.exit(1) - -cmake_filename='CMakeLists.txt' - -# Find all the interesting files in the repository -clang_files = [] -cmake_files = [] - -all_files = subprocess.check_output('git ls-files', shell=True, text=True).strip('\n').split('\n') - -for file in all_files: - if file.endswith('.cpp') or file.endswith('.h'): - clang_files.append(file) - if file.endswith(cmake_filename): - cmake_files.append(file) - -# Run clang and cmake-format on all the interesting files -unformatted_files = [] -for file in clang_files: - # When the file is unformatted, clang-format returns a zero exit code, but a non-empty stderr - result = subprocess.check_output("{} {} -n".format(clang_format, file), shell=True, text=True, stderr=subprocess.STDOUT) - if len(result) > 0: - unformatted_files.append(file) -for file in cmake_files: - # When the file is unformatted, cmake-format returns a non-zero exit code - result = subprocess.run("{} {} --check".format(cmake_format, file), stderr=subprocess.DEVNULL, shell=True) - if result.returncode != 0: - unformatted_files.append(file) - -unformatted_files.sort() - -if unformatted_files: - print("The following files have incorrect formatting:\n") - for file in unformatted_files: - print(file) - sys.exit(1) - -print("All files have correct formatting.") diff --git a/src/SfsClient/sfs-client/scripts/pip.requirements.txt b/src/SfsClient/sfs-client/scripts/pip.requirements.txt deleted file mode 100644 index bae1499f62..0000000000 --- a/src/SfsClient/sfs-client/scripts/pip.requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -clang-format==19.1.1 -cmake-format==0.6.13 \ No newline at end of file diff --git a/src/SfsClient/sfs-client/scripts/setup.sh b/src/SfsClient/sfs-client/scripts/setup.sh deleted file mode 100755 index 27900fbb65..0000000000 --- a/src/SfsClient/sfs-client/scripts/setup.sh +++ /dev/null @@ -1,190 +0,0 @@ -#!/bin/bash - -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# Synopsis: Sets up dependencies required to build and work with the SFS Client. -# -# Description: This script will install all of the dependencies required to build and work with the SFS Client. -# Use this on non-Windows platforms in a bash session. It must be sourced. -# -# Example: -# $ source ./scripts/setup.sh -# - -COLOR_RED="\033[1;31m" -COLOR_CYAN="\033[1;36m" -NO_COLOR="\033[0m" - -header() { echo -e "${COLOR_CYAN}$*${NO_COLOR}"; } -error() { echo -e "${COLOR_RED}$*${NO_COLOR}" >&2; } - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - error "Script is being run directly, it should be sourced instead." - exit 1 -fi - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)" -git_root=$(git -C "$script_dir" rev-parse --show-toplevel) - -sudo apt-get -qq update - -install_with_apt() { - program_to_install=$1 - - if ! sudo apt-get -qq install "$program_to_install"; then - error "Failed to install $program_to_install" - return 1 - fi - return 0 -} - -install_python() { - header "Installing latest Python if it's not installed" - - if ! install_with_apt "python3"; then - return - fi - if ! install_with_apt "python3-pip"; then - return - fi - return 0 -} - -install_pip_dependencies() { - header "\nInstalling dependencies using pip" - - # Upgrade pip and install requirements. Filter out output for dependencies that are already installed - python3 -m pip install --upgrade pip | grep -v -e "already satisfied" -e "Defaulting to user installation" - ret="${PIPESTATUS[0]}" - if [ "$ret" -ne 0 ]; then - return "$ret" - fi - - pip_reqs="$script_dir/pip.requirements.txt" - - pip install -r "$pip_reqs" | grep -v -e "already satisfied" -e "Defaulting to user installation" - ret="${PIPESTATUS[0]}" - if [ "$ret" -ne 0 ]; then - return "$ret" - fi - - return 0 -} - -install_cmake() { - header "\nInstalling cmake if it's not installed" - - if ! install_with_apt "cmake"; then - return - fi - return 0 -} - -install_vcpkg() { - header "\nSetting up vcpkg" - - vcpkg_dir="$git_root/vcpkg" - if [ -d "$vcpkg_dir" ]; then - echo "Checking if vcpkg repo has new commits" - git -C "$vcpkg_dir" pull --show-forced-updates | grep -v 'Already up to date' - else - echo "Cloning vcpkg repo" - git clone https://github.com/microsoft/vcpkg "$vcpkg_dir" - # Needed for the bootstrap and for other vcpkg packages - if ! sudo apt-get -qq install curl zip unzip tar pkg-config; then - return - fi - "$vcpkg_dir/bootstrap-vcpkg.sh" - fi - return 0 -} - -# Dependency for compiling CorrelationVector C++ library -install_uuid() { - header "\nInstalling uuid-dev if it's not installed" - - if ! install_with_apt "pkg-config"; then - return - fi - if ! install_with_apt "uuid-dev"; then - return - fi - return 0 -} - -set_git_hooks() { - header "\nSetting Git hooks" - - hook_dest_dir="$git_root/.git/hooks" - declare -A git_hooks=( ["pre-commit-wrapper.sh"]="pre-commit") - for src in "${!git_hooks[@]}"; do - hook_src="$git_root/$src" - hook_dest="$hook_dest_dir/${git_hooks[$src]}" - - # If the destination doesn't exist or is different than the one in the source, we'll copy it over. - if [ ! -f "$hook_dest" ] || ! cmp -s "$hook_src" "$hook_dest"; then - cp -f "$hook_src" "$hook_dest" - echo "Setup git ${git_hooks[$src]} hook with $hook_src" - fi - done -} - -set_aliases() { - header "\nSetting aliases" - - python_scripts_dir=$(python3 -c 'import os,sysconfig;print(sysconfig.get_path("scripts",f"{os.name}_user"))') - - declare -A aliases=( ["build"]="$git_root/scripts/build.sh" - ["clang-format"]="$python_scripts_dir/clang-format" - ["cmake-format"]="$python_scripts_dir/cmake-format" - ["test"]="$git_root/scripts/test.sh" - ["vcpkg"]="$git_root/vcpkg/vcpkg") - - for alias in "${!aliases[@]}"; do - target="${aliases[$alias]}" - - # If the alias doesn't exist or is set to something different than the one we want to target, we'll add it. - - # If alias doesn't exist, the alias command returns a non-zero result - alias_does_not_exist=false - alias_exists_not_the_same=false - if ! alias "$alias" > /dev/null 2>&1; then - alias_does_not_exist=true - else - output=$(alias "$alias") - expected_output="alias $alias='$target'" - if [ "$output" != "$expected_output" ]; then - alias_exists_not_the_same=true - fi - fi - - if $alias_does_not_exist || $alias_exists_not_the_same; then - eval "alias $alias=$target" - echo "Setup alias $alias" - fi - done -} - -if ! install_python; then - return -fi - -if ! install_pip_dependencies; then - return -fi - -if ! install_cmake; then - return -fi - -if ! install_vcpkg; then - return -fi - -if ! install_uuid; then - return -fi - -set_git_hooks -set_aliases diff --git a/src/SfsClient/sfs-client/scripts/test.sh b/src/SfsClient/sfs-client/scripts/test.sh deleted file mode 100755 index a45069f825..0000000000 --- a/src/SfsClient/sfs-client/scripts/test.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash - -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# Synopsis: Simplifies test commands for the SFS Client. -# -# Description: This script will contain the test commands for the SFS Client. -# Use this on non-Windows platforms in a bash session. -# -# Example: -# $ ./scripts/test.sh -# - -# Ensures script stops on errors -set -e - -if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - error "Script is being sourced, it should be executed instead." - return 1 -fi - -output_on_failure=false - -usage() { echo "Usage: $0 [--output-on-failure]" 1>&2; exit 1; } - -if ! opts=$(getopt \ - --longoptions "output-on-failure" \ - --name "$(basename "$0")" \ - --options "" \ - -- "$@" -); then - usage -fi - -eval set "--$opts" - -while [ $# -gt 0 ]; do - case "$1" in - --output-on-failure) - output_on_failure=true - shift 1 - ;; - *) - break - ;; - esac -done - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)" -git_root=$(git -C "$script_dir" rev-parse --show-toplevel) -build_folder="$git_root/build" - -cmd="ctest --test-dir \"$build_folder/client\"" - -if $output_on_failure ; then - cmd+=" --output-on-failure" -fi - -eval "$cmd" diff --git a/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/portfile.cmake b/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/portfile.cmake deleted file mode 100644 index ffc82dd130..0000000000 --- a/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/portfile.cmake +++ /dev/null @@ -1,37 +0,0 @@ -# This file is a template portfile to be used to consume the sfs-client library using the vcpkg infrastructure - -# Choose one of the consumption formats below. SOURCE_PATH must be defined after either step. - -# 1. Get from Github -# vcpkg_from_github( -# OUT_SOURCE_PATH SOURCE_PATH -# REPO microsoft/sfs-client -# REF -# SHA512 0 # Run vcpkg the first time and this value will be shown in the logs for you to replace -# HEAD_REF main -# ) - -# 2. Use a locally available path, like a subtree'd repository -# set(SOURCE_PATH D:/sfs-client) - -# Preferring static linkage in Windows -if(VCPKG_TARGET_IS_WINDOWS) - vcpkg_check_linkage(ONLY_STATIC_LIBRARY) -endif() - -# Configure and install the library -vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}") -vcpkg_cmake_install() -vcpkg_fixup_pkgconfig() - -# Exposes the package targets like other vcpkg packages do -vcpkg_cmake_config_fixup(PACKAGE_NAME sfsclient CONFIG_PATH lib/cmake/sfsclient) - -# Handle copyright -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") - -# Handle usage -file(COPY "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") - -# Remove duplicated include files -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") diff --git a/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/usage b/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/usage deleted file mode 100644 index a6376e32bb..0000000000 --- a/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/usage +++ /dev/null @@ -1,4 +0,0 @@ -The package sfs-client provides CMake targets: - - find_package(sfsclient CONFIG REQUIRED) - target_link_libraries(main PRIVATE microsoft::sfsclient) diff --git a/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/vcpkg.json b/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/vcpkg.json deleted file mode 100644 index 1cb93fa8c6..0000000000 --- a/src/SfsClient/sfs-client/sfs-client-vcpkg-port/sfs-client/vcpkg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "sfs-client", - "version": "", - "description": "Simple File Solution (SFS) Client", - "homepage": "https://github.com/microsoft/sfs-client", - "license": "MIT", - "dependencies": [ - { - "name": "curl", - "features": [ - "c-ares", - { - "name": "openssl", - "platform": "!windows", - "$comment": "TODO #42: OpenSSL is not needed on Windows yet while we don't have cert pinning. Default is schannel, which uses Windows store" - } - ] - }, - "nlohmann-json", - "correlation-vector-cpp", - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ] -} \ No newline at end of file diff --git a/src/SfsClient/sfs-client/vcpkg-custom-triplets/x64-windows-static-custom.cmake b/src/SfsClient/sfs-client/vcpkg-custom-triplets/x64-windows-static-custom.cmake deleted file mode 100644 index 1318b7aee5..0000000000 --- a/src/SfsClient/sfs-client/vcpkg-custom-triplets/x64-windows-static-custom.cmake +++ /dev/null @@ -1,6 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x64) - -message(STATUS "PORTs are preferred with static linkage...") - -set(VCPKG_CRT_LINKAGE static) -set(VCPKG_LIBRARY_LINKAGE static) diff --git a/src/SfsClient/sfs-client/vcpkg.json b/src/SfsClient/sfs-client/vcpkg.json deleted file mode 100644 index 0d6c4d3fc1..0000000000 --- a/src/SfsClient/sfs-client/vcpkg.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "$in-case-of-update": "If updating this file, please also update the cgmanifest.json file. See DEVELOPMENT.md for more", - "features": { - "tests": { - "description": "Build tests", - "dependencies": [ - "catch2", - "cpp-httplib" - ] - } - }, - "dependencies": [ - "correlation-vector-cpp", - { - "name": "curl", - "features": [ - "c-ares", - { - "name": "openssl", - "platform": "!windows", - "$comment": "TODO #42: OpenSSL is not needed on Windows yet while we don't have cert pinning. Default is schannel, which uses Windows store" - } - ] - }, - "nlohmann-json" - ], - "overrides": [ - { - "name": "c-ares", - "version": "1.19.1" - }, - { - "name": "catch2", - "version": "3.4.0" - }, - { - "name": "cpp-httplib", - "version": "0.15.3" - }, - { - "name": "correlation-vector-cpp", - "version": "1.0" - }, - { - "name": "curl", - "version": "8.9.1" - }, - { - "name": "nlohmann-json", - "version": "3.11.3" - }, - { - "name": "openssl", - "version": "3.1.4#1" - } - ], - "vcpkg-configuration": { - "overlay-triplets": [ - "./vcpkg-custom-triplets" - ] - }, - "builtin-baseline": "f56238700757aa05975e41fa835739c632810f3f" -} \ No newline at end of file diff --git a/src/VcpkgPortOverlay/.gitignore b/src/VcpkgPortOverlay/.gitignore new file mode 100644 index 0000000000..43a24cfd8d --- /dev/null +++ b/src/VcpkgPortOverlay/.gitignore @@ -0,0 +1,6 @@ +# Generated port directories are ignored; recreate with CreatePortOverlay.ps1 +/* +!/.gitignore +!/CreatePortOverlay.ps1 +!/README.md +!/patches diff --git a/src/VcpkgPortOverlay/CreatePortOverlay.ps1 b/src/VcpkgPortOverlay/CreatePortOverlay.ps1 index f3ff774fb8..4b0685c050 100644 --- a/src/VcpkgPortOverlay/CreatePortOverlay.ps1 +++ b/src/VcpkgPortOverlay/CreatePortOverlay.ps1 @@ -1,5 +1,12 @@ # Helper functions for dealing with the port overlay +[CmdletBinding()] +param( + # When provided, serializes concurrent invocations via a named mutex and skips work + # if the stamp file is already up-to-date. Used by VcpkgPortOverlay.proj. + [string]$StampFile +) + $OverlayRoot = $PSScriptRoot $ErrorActionPreference = "Stop" @@ -50,24 +57,27 @@ function Get-GitTreeAsArchive { param( [Parameter(Mandatory)] - [string]$GitTree + [string]$GitTree, + [string]$Repo = 'microsoft/vcpkg' ) - $archiveUri = "https://github.com/microsoft/vcpkg/archive/$gitTree.zip" + $archiveUri = "https://github.com/$Repo/archive/$gitTree.zip" $response = Invoke-WebRequest -Uri $archiveUri $zipStream = [System.IO.MemoryStream]::new($response.Content) $zipArchive = [System.IO.Compression.ZipArchive]::new($zipStream) return $zipArchive } -# Expands an in-memory archive and writes it to disk +# Expands an in-memory archive and writes it to disk. +# If SubPath is specified, only files under that path are extracted. function Expand-ArchiveFromMemory { param( [Parameter(Mandatory)] [System.IO.Compression.ZipArchive]$Archive, [Parameter(Mandatory)] - [string]$Destination + [string]$Destination, + [string]$SubPath ) # Delete existing directory @@ -78,6 +88,10 @@ function Expand-ArchiveFromMemory # Remove length=0 to ignore the directory itself $entries = $archive.Entries | Where-Object { $_.Length -ne 0 } + if ($SubPath) + { + $entries = $entries | Where-Object { $_.FullName -like "*/$SubPath/*" } + } if (-not $entries) { throw "Archive is empty" @@ -109,6 +123,99 @@ function New-PortOverlay Expand-ArchiveFromMemory $archive $portDir } +# Creates a copy of a port from a GitHub repository in this overlay, +# by downloading the repository at a specific commit and extracting a subdirectory. +function New-PortOverlayFromGitHub +{ + param( + [Parameter(Mandatory)] + [string]$Port, + [Parameter(Mandatory)] + [string]$Repo, # as user/repo + [Parameter(Mandatory)] + [string]$Commit, + [Parameter(Mandatory)] + [string]$SubPath + ) + + $archive = Get-GitTreeAsArchive -GitTree $Commit -Repo $Repo + $portDir = Join-Path $OverlayRoot $Port + Expand-ArchiveFromMemory -Archive $archive -Destination $portDir -SubPath $SubPath +} + +# Expands a portfile.cmake that uses a "commented-option" template format, where the active +# function call is commented out. Uncomments the specified function block, strips remaining +# comment lines, and collapses blank lines. +function Expand-PortfileTemplate +{ + param( + [Parameter(Mandatory)] + [string]$Port, + [string]$CommentedFunction = 'vcpkg_from_github' + ) + + $portFilePath = Join-Path $OverlayRoot $Port 'portfile.cmake' + $lines = Get-Content $portFilePath + + $result = [System.Collections.Generic.List[string]]::new() + $inCommentedBlock = $false + $prevWasBlank = $false + + foreach ($line in $lines) + { + if ($line -match "^# $([regex]::Escape($CommentedFunction))\(") + { + $inCommentedBlock = $true + $result.Add("$CommentedFunction(") + $prevWasBlank = $false + continue + } + + if ($inCommentedBlock) + { + if ($line.TrimEnd() -eq '# )') + { + $result.Add(')') + $inCommentedBlock = $false + } + else + { + $uncommented = $line -replace '^# ?', '' + # Strip trailing inline hint comments from template lines + $uncommented = $uncommented -replace ' # .*$', '' + $result.Add($uncommented) + } + $prevWasBlank = $false + continue + } + + # Skip comment-only lines (template instructions, disabled options, etc.) + if ($line -match '^#') { continue } + + if ($line -eq '') + { + if (-not $prevWasBlank -and $result.Count -gt 0) + { + $result.Add('') + } + $prevWasBlank = $true + } + else + { + $result.Add($line) + $prevWasBlank = $false + } + } + + # Remove trailing blank lines + while ($result.Count -gt 0 -and $result[$result.Count - 1] -eq '') + { + $result.RemoveAt($result.Count - 1) + } + + $result | Out-File $portFilePath +} + # Gets a git patch from a GitHub commit function Get-GitHubPatch { @@ -274,6 +381,45 @@ function Add-PatchToPort Add-PatchToPortFile -Port $Port -PatchName $PatchName } +# Adds a patch from the local patches/ directory to a port. +# Patches stored in patches// are committed to the repo and survive port regeneration. +function Add-LocalPatch +{ + param( + [Parameter(Mandatory)] + [string]$Port, + [Parameter(Mandatory)] + [string]$PatchName + ) + + Copy-Item (Join-Path $OverlayRoot 'patches' $Port $PatchName) (Join-Path $OverlayRoot $Port) + + $portFilePath = Join-Path $OverlayRoot $Port 'portfile.cmake' + $lines = Get-Content $portFilePath + $hasPatchesKeyword = $lines | Where-Object { $_ -match '\bPATCHES\b' } + + if ($hasPatchesKeyword) + { + Add-PatchToPortFile -Port $Port -PatchName $PatchName + return + } + + # Add PATCHES keyword and the patch name before the closing paren of vcpkg_from_github + $result = @() + $foundParen = $false + foreach ($line in $lines) + { + if (-not $foundParen -and $line -eq ')') + { + $result += ' PATCHES' + $result += " $PatchName" + $foundParen = $true + } + $result += $line + } + $result | Out-File $portFilePath +} + # Sets the value of an existing function parameter. # For example, REF in vcpkg_from_github function Set-ParameterInPortFile @@ -303,6 +449,61 @@ function Set-ParameterInPortFile $modifiedPortFile | Out-File $portFilePath } +# Sets cmake configure options for a port by expanding the vcpkg_cmake_configure call. +function Set-CmakeConfigureOptions +{ + param( + [Parameter(Mandatory)] + [string]$Port, + [Parameter(Mandatory)] + [string[]]$Options + ) + + $portFilePath = Join-Path $OverlayRoot $Port 'portfile.cmake' + $originalPortFile = Get-Content $portFilePath + + $modifiedPortFile = @() + foreach ($line in $originalPortFile) + { + if ($line -match '^vcpkg_cmake_configure\(SOURCE_PATH') + { + $modifiedPortFile += 'vcpkg_cmake_configure(' + $modifiedPortFile += ' SOURCE_PATH "${SOURCE_PATH}"' + $modifiedPortFile += ' OPTIONS' + foreach ($option in $Options) + { + $modifiedPortFile += " $option" + } + $modifiedPortFile += ')' + } + else + { + $modifiedPortFile += $line + } + } + + $modifiedPortFile | Out-File $portFilePath +} + +# Replaces a string in a file within a port directory. +function Set-PortFilePlaceholder +{ + param( + [Parameter(Mandatory)] + [string]$Port, + [Parameter(Mandatory)] + [string]$File, + [Parameter(Mandatory)] + [string]$Placeholder, + [Parameter(Mandatory)] + [string]$Value + ) + + $filePath = Join-Path $OverlayRoot $Port $File + $content = (Get-Content -Raw $filePath) -replace "<$Placeholder>", $Value + [System.IO.File]::WriteAllText($filePath, $content, [System.Text.Encoding]::UTF8) +} + # Updates the source commit used for a port. # Takes the commit hash, and the hash of the archive with the code that vcpkg will download. function Update-PortSource @@ -336,13 +537,59 @@ function Update-PortVersion $portDefinition | ConvertTo-Json -Depth 5 | Out-File $portJsonPath } -New-PortOverlay cpprestsdk -Version 2.10.18 -PortVersion 4 -Add-PatchToPort cpprestsdk -PatchRepo 'microsoft/winget-cli' -PatchCommit '888b4ed8f4f7d25cb05a47210e083fe29348163b' -PatchName 'add-server-certificate-validation.patch' -PatchRoot 'src/cpprestsdk/cpprestsdk' +# Acquire mutex if running from MSBuild (StampFile provided) to serialize parallel project builds +$_mutex = $null +if ($StampFile) { + $_mutex = [System.Threading.Mutex]::new($false, 'Local\WingetVcpkgPortOverlay') + try { $_mutex.WaitOne() | Out-Null } catch [System.Threading.AbandonedMutexException] {} + + # Another process may have already rebuilt the overlay while we waited; skip if so. + if (Test-Path $StampFile) { + $stampTime = (Get-Item $StampFile).LastWriteTime + + $overlayInputs = @($PSCommandPath) + if (Test-Path $OverlayRoot\patches) { + $overlayInputs += Get-ChildItem -Path $OverlayRoot\patches -Recurse -File + } + + if (-not ($overlayInputs | Where-Object { $_.LastWriteTime -gt $stampTime } | Select-Object -First 1)) { + $_mutex.ReleaseMutex() + return + } + } +} -New-PortOverlay detours -Version 4.0.1 -PortVersion 8 -Update-PortSource detours -RefPattern 'v4.0.1' -Commit '404c153ff390cb14f1787c7feeb4908c6d79b0ab' -SourceHash '1f3f26657927fa153116dce13dbfa3319ea368e6c9017f4999b6ec24d6356c335b3d5326718d3ec707b92832763ffea092088df52596f016d7ca9b8127f7033d' -Remove-PortPatches detours +try { + New-PortOverlay cpprestsdk -Version 2.10.18 -PortVersion 4 + Add-LocalPatch cpprestsdk 'add-server-certificate-validation.patch' -New-PortOverlay libyaml -Version 0.2.5 -PortVersion 5 -Update-PortSource libyaml -Commit '840b65c40675e2d06bf40405ad3f12dec7f35923' -SourceHash 'de85560312d53a007a2ddf1fe403676bbd34620480b1ba446b8c16bb366524ba7a6ed08f6316dd783bf980d9e26603a9efc82f134eb0235917b3be1d3eb4b302' -Update-PortVersion libyaml + New-PortOverlay detours -Version 4.0.1 -PortVersion 8 + Update-PortSource detours -RefPattern 'v4.0.1' -Commit '404c153ff390cb14f1787c7feeb4908c6d79b0ab' -SourceHash '1f3f26657927fa153116dce13dbfa3319ea368e6c9017f4999b6ec24d6356c335b3d5326718d3ec707b92832763ffea092088df52596f016d7ca9b8127f7033d' + Remove-PortPatches detours + + New-PortOverlay libyaml -Version 0.2.5 -PortVersion 5 + Update-PortSource libyaml -Commit '840b65c40675e2d06bf40405ad3f12dec7f35923' -SourceHash 'de85560312d53a007a2ddf1fe403676bbd34620480b1ba446b8c16bb366524ba7a6ed08f6316dd783bf980d9e26603a9efc82f134eb0235917b3be1d3eb4b302' + Update-PortVersion libyaml + + # sfs-client is not in the official vcpkg registry. + # The port is based on the template from the sfs-client repository. + # See: https://github.com/microsoft/sfs-client/tree/main/sfs-client-vcpkg-port/sfs-client + $SfsClientCommit = '0e27525d597c730e71646fd0b15bdc8c8503f24d' + $SfsClientSha512 = 'd926d7fdbbd120cbcbd9732a3300cccfeed4a90d6b94456d73a70675df3578a91127f7e9f310fe68d18fa34bb997c29c8455e586d81a2ba404cf19193a80ca6e' + $SfsClientVersion = '1.1.0' + + New-PortOverlayFromGitHub 'sfs-client' -Repo 'microsoft/sfs-client' -Commit $SfsClientCommit -SubPath 'sfs-client-vcpkg-port/sfs-client' + Expand-PortfileTemplate 'sfs-client' + Set-PortFilePlaceholder 'sfs-client' 'portfile.cmake' -Placeholder 'commit-id' -Value $SfsClientCommit + Set-ParameterInPortFile 'sfs-client' -ParameterName 'SHA512' -CurrentValuePattern '0' -NewValue $SfsClientSha512 + Set-CmakeConfigureOptions 'sfs-client' -Options @('-DSFS_BUILD_TESTS=OFF', '-DSFS_BUILD_SAMPLES=OFF') + Set-PortFilePlaceholder 'sfs-client' 'vcpkg.json' -Placeholder 'VERSION' -Value $SfsClientVersion + + Add-LocalPatch 'sfs-client' 'remove-unconditional-toolchain-override.patch' + + if ($StampFile) { + $null = New-Item -ItemType File -Path $StampFile -Force + } +} finally { + if ($_mutex) { $_mutex.ReleaseMutex() } +} diff --git a/src/VcpkgPortOverlay/README.md b/src/VcpkgPortOverlay/README.md index 9b38261af4..6067b54151 100644 --- a/src/VcpkgPortOverlay/README.md +++ b/src/VcpkgPortOverlay/README.md @@ -25,6 +25,13 @@ Changes: * New source commit: https://github.com/microsoft/Detours/commit/404c153ff390cb14f1787c7feeb4908c6d79b0ab * Remove the patch on the official port as it is already present in the newer commit +## sfs-client + +sfs-client is not in the official vcpkg registry. The port is based on the [template included in the sfs-client repository](https://github.com/microsoft/sfs-client/tree/main/sfs-client-vcpkg-port/sfs-client). + +We use version 1.1.0 at commit https://github.com/microsoft/sfs-client/commit/0e27525d597c730e71646fd0b15bdc8c8503f24d. +Tests and samples are disabled (`-DSFS_BUILD_TESTS=OFF -DSFS_BUILD_SAMPLES=OFF`). + ## libyaml We use an unreleased version that fixes a vulnerability. diff --git a/src/VcpkgPortOverlay/cpprestsdk/add-server-certificate-validation.patch b/src/VcpkgPortOverlay/cpprestsdk/add-server-certificate-validation.patch deleted file mode 100644 index 84149843d2..0000000000 --- a/src/VcpkgPortOverlay/cpprestsdk/add-server-certificate-validation.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 888b4ed8f4f7d25cb05a47210e083fe29348163b Mon Sep 17 00:00:00 2001 -From: JohnMcPMS -Date: Wed, 27 Jul 2022 18:03:45 -0700 -Subject: [PATCH] Server certificate pinning for Store source (#2347) - -This change adds a generic certificate chain verification infrastructure for pinning certificate chains. It is specifically used to pin the Microsoft Store source by default. More sources may be pinned later, but currently the packaged index is less in need of it because it is already signed. - -The pinning configuration consists of 1 or more chains, only one of which needs to successfully validate the incoming certificate. This allows for rolling to a new certificate when needed. Each chain consists of a fixed set of certificates, which can each be configured to validate any or all of the following properties: - -- Public Key -- Subject -- Issuer - -If the certificate is configured to validate none of the values, it will allow any certificate through. - -An admin setting is added to disable pinning, both as an emergency measure in the event that there is a bug or rolled certificate that was not communicated, but also because there are test scenarios where the user actively wants to disable it (HTTPS redirection via something like Fiddler). - -The configuration can be loaded from JSON for future dynamic configuration, but it is currently only as a test hook to enable configuration via Group Policy. - -In order to better secure the source by default, reconfiguring (remove then add) the Store source manually will convert it back to the built-in values. This includes the pinning configuration. - -It was necessary to modify the cpprestsdk subtree to add a new callback. This enables the request handle to be passed back to our code when the server certificate is first available. We can then check the server certificate against the configured pinning chain, making a decision to terminate the request before it is sent. ---- - .github/actions/spelling/allow.txt | 7 +- - .github/actions/spelling/expect.txt | 10 + - src/AppInstallerCLI.sln | 8 + - src/AppInstallerCLIE2ETests/BaseCommand.cs | 6 +- - src/AppInstallerCLIE2ETests/Constants.cs | 11 + - .../GroupPolicyHelper.cs | 44 ++ - src/AppInstallerCLIE2ETests/SearchCommand.cs | 73 ++- - src/AppInstallerCLIE2ETests/SetUpFixture.cs | 2 +- - src/AppInstallerCLIE2ETests/SourceCommand.cs | 4 +- - src/AppInstallerCLIE2ETests/TestCommon.cs | 56 +- - src/AppInstallerCLIE2ETests/TestIndexSetup.cs | 6 +- - .../AppInstallerCLITests.vcxproj | 2 + - .../AppInstallerCLITests.vcxproj.filters | 3 + - src/AppInstallerCLITests/Certificates.cpp | 185 ++++++ - src/AppInstallerCLITests/Command.cpp | 2 +- - src/AppInstallerCLITests/Completion.cpp | 48 +- - src/AppInstallerCLITests/GroupPolicy.cpp | 60 +- - src/AppInstallerCLITests/HttpClientHelper.cpp | 23 + - src/AppInstallerCLITests/Sources.cpp | 41 ++ - src/AppInstallerCLITests/Strings.cpp | 9 + - src/AppInstallerCommonCore/AdminSettings.cpp | 146 +++-- - .../AppInstallerCommonCore.vcxproj | 4 + - .../AppInstallerCommonCore.vcxproj.filters | 9 + - .../AppInstallerStrings.cpp | 39 ++ - src/AppInstallerCommonCore/Certificates.cpp | 549 ++++++++++++++++++ - src/AppInstallerCommonCore/Errors.cpp | 2 + - src/AppInstallerCommonCore/GroupPolicy.cpp | 13 + - .../JsonSchemaValidation.cpp | 36 +- - .../Manifest/ManifestSchemaValidation.cpp | 3 +- - .../Public/AppInstallerErrors.h | 1 + - .../Public/AppInstallerStrings.h | 6 + - .../Public/winget/AdminSettings.h | 1 + - .../Public/winget/Certificates.h | 153 +++++ - .../Public/winget/GroupPolicy.h | 11 +- - .../Public/winget/JsonSchemaValidation.h | 5 +- - .../Public/winget/Resources.h | 68 ++- - src/AppInstallerCommonCore/Resources.cpp | 58 ++ - src/AppInstallerCommonCore/SHA256.cpp | 33 +- - src/AppInstallerCommonCore/pch.h | 1 + - .../AppInstallerRepositoryCore.vcxproj | 4 +- - .../Public/winget/RepositorySource.h | 8 +- - .../RepositorySource.cpp | 24 +- - .../Rest/RestSourceFactory.cpp | 6 +- - .../Rest/Schema/HttpClientHelper.cpp | 26 +- - .../Rest/Schema/HttpClientHelper.h | 10 +- - src/AppInstallerRepositoryCore/SourceList.cpp | 54 ++ - src/AppInstallerRepositoryCore/SourceList.h | 1 + - src/AppInstallerRepositoryCore/pch.h | 1 + - .../CertificateResources.h | 9 + - .../CertificateResources.rc | 69 +++ - .../CertificateResources.vcxitems | 28 + - .../CertificateResources.vcxitems.filters | 26 + - .../StoreIntermediate1.cer | Bin 0 -> 1527 bytes - src/CertificateResources/StoreLeaf1.cer | Bin 0 -> 2642 bytes - src/CertificateResources/StoreRoot1.cer | Bin 0 -> 914 bytes - src/CertificateResources/resource.h | 14 + - src/LocalhostWebServer/Program.cs | 14 +- - .../Run-LocalhostWebServer.ps1 | 2 +- - src/LocalhostWebServer/Startup.cs | 2 + - .../Properties/Resources.Designer.cs | 2 +- - .../WindowsPackageManager.vcxproj | 1 + - .../Release/include/cpprest/http_client.h | 27 + - .../src/http/client/http_client_winhttp.cpp | 12 + - 63 files changed, 1852 insertions(+), 226 deletions(-) - create mode 100644 src/AppInstallerCLITests/Certificates.cpp - create mode 100644 src/AppInstallerCommonCore/Certificates.cpp - create mode 100644 src/AppInstallerCommonCore/Public/winget/Certificates.h - create mode 100644 src/AppInstallerCommonCore/Resources.cpp - create mode 100644 src/CertificateResources/CertificateResources.h - create mode 100644 src/CertificateResources/CertificateResources.rc - create mode 100644 src/CertificateResources/CertificateResources.vcxitems - create mode 100644 src/CertificateResources/CertificateResources.vcxitems.filters - create mode 100644 src/CertificateResources/StoreIntermediate1.cer - create mode 100644 src/CertificateResources/StoreLeaf1.cer - create mode 100644 src/CertificateResources/StoreRoot1.cer - create mode 100644 src/CertificateResources/resource.h - -diff --git a/Release/include/cpprest/http_client.h b/Release/include/cpprest/http_client.h -index fb7c6067ab..b862a5778f 100644 ---- a/Release/include/cpprest/http_client.h -+++ b/Release/include/cpprest/http_client.h -@@ -362,6 +362,32 @@ class http_client_config - if (m_set_user_nativehandle_options) m_set_user_nativehandle_options(handle); - } - -+ /// -+ /// Sets a callback to enable custom handling when the server certificate is available. -+ /// -+ /// -+ /// The native_handle is the following type depending on the underlying platform: -+ /// Windows Desktop, WinHTTP - HINTERNET !!! Is only implemented to call in here !!! -+ /// Windows Runtime, WinRT - IXMLHTTPRequest2 * -+ /// All other platforms, Boost.Asio: -+ /// https - boost::asio::ssl::stream * -+ /// http - boost::asio::ip::tcp::socket * -+ /// -+ /// A user callback allowing for validation of the server certificate. -+ void set_nativehandle_servercertificate_validation(const std::function& callback) -+ { -+ m_nativehandle_servercertificate_validation = callback; -+ } -+ -+ /// -+ /// Invokes a user's callback to validate the server certificate. -+ /// -+ /// A internal implementation handle. -+ void invoke_nativehandle_servercertificate_validation(native_handle handle) const -+ { -+ if (m_nativehandle_servercertificate_validation) m_nativehandle_servercertificate_validation(handle); -+ } -+ - #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) - /// - /// Sets a callback to enable custom setting of the ssl context, at construction time. -@@ -418,6 +444,7 @@ class http_client_config - - std::function m_set_user_nativehandle_options; - std::function m_set_user_nativesessionhandle_options; -+ std::function m_nativehandle_servercertificate_validation; - - #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) - std::function m_ssl_context_callback; -diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp -index d6cdb5384a..5a517ec334 100644 ---- a/Release/src/http/client/http_client_winhttp.cpp -+++ b/Release/src/http/client/http_client_winhttp.cpp -@@ -2039,6 +2039,18 @@ class winhttp_client final : public _http_client_communicator - case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: - { - p_request_context->on_send_request_validate_cn(); -+ -+ try -+ { -+ p_request_context->m_http_client->client_config().invoke_nativehandle_servercertificate_validation(hRequestHandle); -+ } -+ catch (...) -+ { -+ p_request_context->report_exception(std::current_exception()); -+ p_request_context->cleanup(); -+ return; -+ } -+ - return; - } - case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: - diff --git a/src/VcpkgPortOverlay/cpprestsdk/fix-find-openssl.patch b/src/VcpkgPortOverlay/cpprestsdk/fix-find-openssl.patch deleted file mode 100644 index ec420e6771..0000000000 --- a/src/VcpkgPortOverlay/cpprestsdk/fix-find-openssl.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/Release/cmake/cpprest_find_openssl.cmake b/Release/cmake/cpprest_find_openssl.cmake -index 9333663..c1df089 100644 ---- a/Release/cmake/cpprest_find_openssl.cmake -+++ b/Release/cmake/cpprest_find_openssl.cmake -@@ -36,8 +36,11 @@ function(cpprest_find_openssl) - # Prefer a homebrew version of OpenSSL over the one in /usr/lib - file(GLOB OPENSSL_ROOT_DIR /usr/local/Cellar/openssl*/*) - # Prefer the latest (make the latest one first) -- list(REVERSE OPENSSL_ROOT_DIR) -- list(GET OPENSSL_ROOT_DIR 0 OPENSSL_ROOT_DIR) -+ if(OPENSSL_ROOT_DIR) -+ # Prefer the latest (make the latest one first) -+ list(REVERSE OPENSSL_ROOT_DIR) -+ list(GET OPENSSL_ROOT_DIR 0 OPENSSL_ROOT_DIR) -+ endif() - endif() - # This should prevent linking against the system provided 0.9.8y - message(STATUS "OPENSSL_ROOT_DIR = ${OPENSSL_ROOT_DIR}") diff --git a/src/VcpkgPortOverlay/cpprestsdk/fix-uwp.patch b/src/VcpkgPortOverlay/cpprestsdk/fix-uwp.patch deleted file mode 100644 index c7e77ebf6c..0000000000 --- a/src/VcpkgPortOverlay/cpprestsdk/fix-uwp.patch +++ /dev/null @@ -1,28 +0,0 @@ -diff --git a/Release/CMakeLists.txt b/Release/CMakeLists.txt -index b8f3809..3857cfc 100644 ---- a/Release/CMakeLists.txt -+++ b/Release/CMakeLists.txt -@@ -187,7 +187,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /profile /OPT:REF /OPT:ICF") - - if (WINDOWS_STORE OR WINDOWS_PHONE) -- add_compile_options(/ZW) -+ # add_compile_options(/ZW) - else() - if (NOT (MSVC_VERSION LESS 1920)) - add_compile_options(/permissive-) -diff --git a/Release/src/CMakeLists.txt b/Release/src/CMakeLists.txt -index 128f6d6..098d33f 100644 ---- a/Release/src/CMakeLists.txt -+++ b/Release/src/CMakeLists.txt -@@ -47,6 +47,10 @@ target_include_directories(cpprest - pch - ) - -+if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND (WINDOWS_STORE OR WINDOWS_PHONE)) -+ target_compile_options(cpprest PUBLIC /ZW) -+endif() -+ - ## Sub-components - # Websockets component - if(CPPREST_WEBSOCKETS_IMPL STREQUAL "none") diff --git a/src/VcpkgPortOverlay/cpprestsdk/fix_narrowing.patch b/src/VcpkgPortOverlay/cpprestsdk/fix_narrowing.patch deleted file mode 100644 index 975a759ca9..0000000000 --- a/src/VcpkgPortOverlay/cpprestsdk/fix_narrowing.patch +++ /dev/null @@ -1,50 +0,0 @@ -diff --git a/Release/src/CMakeLists.txt b/Release/src/CMakeLists.txt -index e15aeb7fc..128f6d6af 100644 ---- a/Release/src/CMakeLists.txt -+++ b/Release/src/CMakeLists.txt -@@ -185,12 +185,12 @@ endif() - - configure_pch(cpprest stdafx.h pch/stdafx.cpp /Zm120) - --if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") -+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND NOT MSVC) - if(WERROR) - target_compile_options(cpprest PRIVATE -Werror) - endif() - target_compile_options(cpprest PRIVATE -pedantic ${WARNINGS}) --elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") -+elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") - if(WERROR) - target_compile_options(cpprest PRIVATE /WX ${WARNINGS}) - endif() -diff --git a/Release/src/streams/fileio_win32.cpp b/Release/src/streams/fileio_win32.cpp -index 057dd9b67..a65439cb7 100644 ---- a/Release/src/streams/fileio_win32.cpp -+++ b/Release/src/streams/fileio_win32.cpp -@@ -616,7 +616,7 @@ size_t _fill_buffer_fsb(_In_ _file_info_impl* fInfo, - // pending - return read; - -- case (-1): -+ case ((size_t)(-1)): - // error - delete cb; - return read; -@@ -668,7 +668,7 @@ size_t _fill_buffer_fsb(_In_ _file_info_impl* fInfo, - // pending - return read; - -- case (-1): -+ case ((size_t)(-1)): - // error - delete cb; - return read; -@@ -719,7 +719,7 @@ size_t _fill_buffer_fsb(_In_ _file_info_impl* fInfo, - // pending - return read; - -- case (-1): -+ case ((size_t)(-1)): - // error - delete cb; - return read; diff --git a/src/VcpkgPortOverlay/cpprestsdk/portfile.cmake b/src/VcpkgPortOverlay/cpprestsdk/portfile.cmake deleted file mode 100644 index 0568ae7e8f..0000000000 --- a/src/VcpkgPortOverlay/cpprestsdk/portfile.cmake +++ /dev/null @@ -1,61 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO Microsoft/cpprestsdk - REF 122d09549201da5383321d870bed45ecb9e168c5 - SHA512 c9ded33d3c67880e2471e479a38b40a14a9ff45d241e928b6339eca697b06ad621846260eca47b6b1b8a2bc9ab7bf4fea8d3e8e795cd430d8839beb530e16dd7 - HEAD_REF master - PATCHES - fix-find-openssl.patch - fix_narrowing.patch - fix-uwp.patch - add-server-certificate-validation.patch -) - -set(OPTIONS) -if(NOT VCPKG_TARGET_IS_UWP) - SET(WEBSOCKETPP_PATH "${CURRENT_INSTALLED_DIR}/share/websocketpp") - list(APPEND OPTIONS - -DWEBSOCKETPP_CONFIG=${WEBSOCKETPP_PATH} - -DWEBSOCKETPP_CONFIG_VERSION=${WEBSOCKETPP_PATH}) -endif() - -vcpkg_check_features( - OUT_FEATURE_OPTIONS FEATURE_OPTIONS - INVERTED_FEATURES - brotli CPPREST_EXCLUDE_BROTLI - compression CPPREST_EXCLUDE_COMPRESSION - websockets CPPREST_EXCLUDE_WEBSOCKETS -) - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}/Release" - ${configure_opts} - OPTIONS - ${OPTIONS} - ${FEATURE_OPTIONS} - -DBUILD_TESTS=OFF - -DBUILD_SAMPLES=OFF - -DCPPREST_EXPORT_DIR=share/cpprestsdk - -DWERROR=OFF - -DPKG_CONFIG_EXECUTABLE=FALSE - OPTIONS_DEBUG - -DCPPREST_INSTALL_HEADERS=OFF -) - -vcpkg_cmake_install() - -vcpkg_copy_pdbs() - -vcpkg_cmake_config_fixup(CONFIG_PATH "lib/share/${PORT}") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/lib/share" "${CURRENT_PACKAGES_DIR}/lib/share") - -if (VCPKG_LIBRARY_LINKAGE STREQUAL static) - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/cpprest/details/cpprest_compat.h" - "#ifdef _NO_ASYNCRTIMP" "#if 1") -endif() - -file(INSTALL "${SOURCE_PATH}/license.txt" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/src/VcpkgPortOverlay/cpprestsdk/test.patch b/src/VcpkgPortOverlay/cpprestsdk/test.patch deleted file mode 100644 index c5507e88ad..0000000000 --- a/src/VcpkgPortOverlay/cpprestsdk/test.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/Release/src/CMakeLists.txt b/Release/src/CMakeLists.txt -index 098d33f..7f1f650 100644 ---- a/Release/src/CMakeLists.txt -+++ b/Release/src/CMakeLists.txt -@@ -218,15 +218,15 @@ if (WIN32 AND NOT WINDOWS_STORE AND NOT WINDOWS_PHONE) - crypt32.lib - ) - elseif(WINDOWS_STORE) -- if(NOT CMAKE_GENERATOR MATCHES "Visual Studio .*") -- target_compile_definitions(cpprest PRIVATE -DWINAPI_FAMILY=WINAPI_FAMILY_PC_APP) -+ -+ target_compile_definitions(cpprest PUBLIC -DWINAPI_FAMILY=WINAPI_FAMILY_PC_APP) - get_target_property(LINK_FLAGS cpprest LINK_FLAGS) - if(NOT LINK_FLAGS) - set(LINK_FLAGS "") - endif() - set(LINK_FLAGS "${LINK_FLAGS} /APPCONTAINER") - set_target_properties(cpprest PROPERTIES LINK_FLAGS "${LINK_FLAGS}") -- endif() -+ - endif() - - set_target_properties(cpprest PROPERTIES OUTPUT_NAME "cpprest${CPPREST_ABI_TAG}") diff --git a/src/VcpkgPortOverlay/cpprestsdk/vcpkg.json b/src/VcpkgPortOverlay/cpprestsdk/vcpkg.json deleted file mode 100644 index a270085312..0000000000 --- a/src/VcpkgPortOverlay/cpprestsdk/vcpkg.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "name": "cpprestsdk", - "version": "2.10.18", - "port-version": 4, - "description": [ - "C++11 JSON, REST, and OAuth library", - "The C++ REST SDK is a Microsoft project for cloud-based client-server communication in native code using a modern asynchronous C++ API design. This project aims to help C++ developers connect to and interact with services." - ], - "homepage": "https://github.com/Microsoft/cpprestsdk", - "license": "MIT", - "dependencies": [ - { - "name": "boost-asio", - "platform": "!uwp & !windows" - }, - { - "name": "boost-chrono", - "platform": "!uwp & !windows" - }, - { - "name": "boost-date-time", - "platform": "!uwp & !windows" - }, - { - "name": "boost-filesystem", - "platform": "!uwp & !windows" - }, - { - "name": "boost-random", - "platform": "!uwp & !windows" - }, - { - "name": "boost-regex", - "platform": "!uwp & !windows" - }, - { - "name": "boost-system", - "platform": "!uwp & !windows" - }, - { - "name": "boost-thread", - "platform": "!uwp & !windows" - }, - { - "name": "openssl", - "platform": "!uwp & !windows" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "brotli", - "platform": "windows" - }, - "compression" - ], - "features": { - "brotli": { - "description": "Brotli compression support", - "dependencies": [ - "brotli", - { - "name": "cpprestsdk", - "default-features": false, - "features": [ - "compression" - ] - } - ] - }, - "compression": { - "description": "HTTP Compression support", - "dependencies": [ - "zlib" - ] - }, - "websockets": { - "description": "Websockets support", - "dependencies": [ - { - "name": "boost-date-time", - "platform": "!uwp" - }, - { - "name": "boost-regex", - "platform": "!uwp" - }, - { - "name": "boost-system", - "platform": "!uwp" - }, - { - "name": "cpprestsdk", - "default-features": false, - "features": [ - "compression" - ] - }, - { - "name": "openssl", - "platform": "!uwp" - }, - { - "name": "websocketpp", - "platform": "!uwp" - } - ] - } - } -} diff --git a/src/VcpkgPortOverlay/detours/find-jmp-bounds-arm64.patch b/src/VcpkgPortOverlay/detours/find-jmp-bounds-arm64.patch deleted file mode 100644 index 21390cec05..0000000000 --- a/src/VcpkgPortOverlay/detours/find-jmp-bounds-arm64.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/src/detours.cpp b/src/detours.cpp -index 8345c4d..3cd0e9d 100644 ---- a/src/detours.cpp -+++ b/src/detours.cpp -@@ -974,6 +974,19 @@ inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals) - return pbCode; - } - -+inline void detour_find_jmp_bounds(PBYTE pbCode, -+ PDETOUR_TRAMPOLINE *ppLower, -+ PDETOUR_TRAMPOLINE *ppUpper) -+{ -+ // We have to place trampolines within +/- 2GB of code. -+ ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode); -+ ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode); -+ DETOUR_TRACE(("[%p..%p..%p]\n", lo, pbCode, hi)); -+ -+ *ppLower = (PDETOUR_TRAMPOLINE)lo; -+ *ppUpper = (PDETOUR_TRAMPOLINE)hi; -+} -+ - inline BOOL detour_does_code_end_function(PBYTE pbCode) - { - ULONG Opcode = fetch_opcode(pbCode); diff --git a/src/VcpkgPortOverlay/detours/portfile.cmake b/src/VcpkgPortOverlay/detours/portfile.cmake deleted file mode 100644 index 1a025f9a04..0000000000 --- a/src/VcpkgPortOverlay/detours/portfile.cmake +++ /dev/null @@ -1,34 +0,0 @@ -vcpkg_check_linkage(ONLY_STATIC_LIBRARY) - -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO microsoft/Detours - REF 404c153ff390cb14f1787c7feeb4908c6d79b0ab # Unreleased - SHA512 1f3f26657927fa153116dce13dbfa3319ea368e6c9017f4999b6ec24d6356c335b3d5326718d3ec707b92832763ffea092088df52596f016d7ca9b8127f7033d - HEAD_REF master -) - -vcpkg_build_nmake( - SOURCE_PATH "${SOURCE_PATH}" - PROJECT_SUBPATH "src" - PROJECT_NAME "Makefile" - OPTIONS "PROCESSOR_ARCHITECTURE=${VCPKG_TARGET_ARCHITECTURE}" - OPTIONS_RELEASE "DETOURS_CONFIG=Release" - OPTIONS_DEBUG "DETOURS_CONFIG=Debug" -) - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(INSTALL "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/lib.${VCPKG_TARGET_ARCHITECTURE}Release/" DESTINATION "${CURRENT_PACKAGES_DIR}/lib") -endif() -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(INSTALL "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/lib.${VCPKG_TARGET_ARCHITECTURE}Debug/" DESTINATION "${CURRENT_PACKAGES_DIR}/debug/lib") -endif() - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(INSTALL "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/include/" DESTINATION "${CURRENT_PACKAGES_DIR}/include" RENAME detours) -else() - file(INSTALL "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/include/" DESTINATION "${CURRENT_PACKAGES_DIR}/include" RENAME detours) -endif() - -file(INSTALL "${SOURCE_PATH}/LICENSE.md" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") diff --git a/src/VcpkgPortOverlay/detours/usage b/src/VcpkgPortOverlay/detours/usage deleted file mode 100644 index 00f978fb68..0000000000 --- a/src/VcpkgPortOverlay/detours/usage +++ /dev/null @@ -1,7 +0,0 @@ -detours can be used from CMake via: - - find_path(DETOURS_INCLUDE_DIRS "detours/detours.h") - find_library(DETOURS_LIBRARY detours REQUIRED) - - target_include_directories(main PRIVATE ${DETOURS_INCLUDE_DIRS}) - target_link_libraries(main PRIVATE ${DETOURS_LIBRARY}) diff --git a/src/VcpkgPortOverlay/detours/vcpkg.json b/src/VcpkgPortOverlay/detours/vcpkg.json deleted file mode 100644 index b0f745daf6..0000000000 --- a/src/VcpkgPortOverlay/detours/vcpkg.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "detours", - "version": "4.0.1", - "port-version": 8, - "description": "Detours is a software package for monitoring and instrumenting API calls on Windows.", - "homepage": "https://github.com/microsoft/Detours", - "license": "MIT", - "supports": "windows & !uwp" -} diff --git a/src/VcpkgPortOverlay/libyaml/export-pkgconfig.patch b/src/VcpkgPortOverlay/libyaml/export-pkgconfig.patch deleted file mode 100644 index 5e0d5a3b74..0000000000 --- a/src/VcpkgPortOverlay/libyaml/export-pkgconfig.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 4f81148..8006536 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -158,3 +158,11 @@ install( - FILES ${config_version_file} - DESTINATION ${INSTALL_CMAKE_DIR} COMPONENT Development - ) -+ -+set(prefix ${CMAKE_INSTALL_PREFIX}) -+set(exec_prefix ${CMAKE_INSTALL_PREFIX}) -+set(includedir ${CMAKE_INSTALL_PREFIX}/include) -+set(libdir ${CMAKE_INSTALL_PREFIX}/lib) -+set(PACKAGE_VERSION 0.1) -+configure_file(yaml-0.1.pc.in ${PROJECT_BINARY_DIR}/yaml-0.1.pc @ONLY) -+install(FILES ${PROJECT_BINARY_DIR}/yaml-0.1.pc DESTINATION lib/pkgconfig) diff --git a/src/VcpkgPortOverlay/libyaml/fix-POSIX_name.patch b/src/VcpkgPortOverlay/libyaml/fix-POSIX_name.patch deleted file mode 100644 index 0aa6d211c2..0000000000 --- a/src/VcpkgPortOverlay/libyaml/fix-POSIX_name.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/api.c b/src/api.c -index e793b08..6f16fc5 100644 ---- a/src/api.c -+++ b/src/api.c -@@ -63,7 +63,7 @@ yaml_strdup(const yaml_char_t *str) - if (!str) - return NULL; - -- return (yaml_char_t *)strdup((char *)str); -+ return (yaml_char_t *)_strdup((char *)str); - } - - /* diff --git a/src/VcpkgPortOverlay/libyaml/portfile.cmake b/src/VcpkgPortOverlay/libyaml/portfile.cmake deleted file mode 100644 index 7835356ac9..0000000000 --- a/src/VcpkgPortOverlay/libyaml/portfile.cmake +++ /dev/null @@ -1,33 +0,0 @@ -if(VCPKG_TARGET_IS_WINDOWS) - set(PATCHES fix-POSIX_name.patch) -endif() - -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO yaml/libyaml - REF 840b65c40675e2d06bf40405ad3f12dec7f35923 # Unreleased - SHA512 de85560312d53a007a2ddf1fe403676bbd34620480b1ba446b8c16bb366524ba7a6ed08f6316dd783bf980d9e26603a9efc82f134eb0235917b3be1d3eb4b302 - HEAD_REF master - PATCHES - ${PATCHES} - export-pkgconfig.patch -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - -DBUILD_TESTING=OFF - -DINSTALL_CMAKE_DIR=share/yaml -) - -vcpkg_cmake_install() - -vcpkg_copy_pdbs() - -vcpkg_cmake_config_fixup(PACKAGE_NAME yaml CONFIG_PATH share/yaml) - -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include" "${CURRENT_PACKAGES_DIR}/include/config.h" "${CURRENT_PACKAGES_DIR}/debug/share") - -configure_file("${SOURCE_PATH}/License" "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" COPYONLY) diff --git a/src/VcpkgPortOverlay/libyaml/vcpkg.json b/src/VcpkgPortOverlay/libyaml/vcpkg.json deleted file mode 100644 index 463182d0bc..0000000000 --- a/src/VcpkgPortOverlay/libyaml/vcpkg.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "libyaml", - "version": "0.2.5", - "port-version": 6, - "description": "A C library for parsing and emitting YAML.", - "homepage": "https://github.com/yaml/libyaml", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ] -} diff --git a/src/VcpkgPortOverlay/patches/cpprestsdk/add-server-certificate-validation.patch b/src/VcpkgPortOverlay/patches/cpprestsdk/add-server-certificate-validation.patch new file mode 100644 index 0000000000..c5eb4d08da --- /dev/null +++ b/src/VcpkgPortOverlay/patches/cpprestsdk/add-server-certificate-validation.patch @@ -0,0 +1,69 @@ +diff --git a/Release/include/cpprest/http_client.h b/Release/include/cpprest/http_client.h +index fb7c6067ab..b862a5778f 100644 +--- a/Release/include/cpprest/http_client.h ++++ b/Release/include/cpprest/http_client.h +@@ -362,6 +362,32 @@ class http_client_config + if (m_set_user_nativehandle_options) m_set_user_nativehandle_options(handle); + } + ++ /// ++ /// Sets a callback to enable custom handling when the server certificate is available. ++ /// ++ /// ++ /// The native_handle is the following type depending on the underlying platform: ++ /// Windows Desktop, WinHTTP - HINTERNET !!! Is only implemented to call in here !!! ++ /// Windows Runtime, WinRT - IXMLHTTPRequest2 * ++ /// All other platforms, Boost.Asio: ++ /// https - boost::asio::ssl::stream * ++ /// http - boost::asio::ip::tcp::socket * ++ /// ++ /// A user callback allowing for validation of the server certificate. ++ void set_nativehandle_servercertificate_validation(const std::function& callback) ++ { ++ m_nativehandle_servercertificate_validation = callback; ++ } ++ ++ /// ++ /// Invokes a user's callback to validate the server certificate. ++ /// ++ /// A internal implementation handle. ++ void invoke_nativehandle_servercertificate_validation(native_handle handle) const ++ { ++ if (m_nativehandle_servercertificate_validation) m_nativehandle_servercertificate_validation(handle); ++ } ++ + #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) + /// + /// Sets a callback to enable custom setting of the ssl context, at construction time. +@@ -418,6 +444,7 @@ class http_client_config + + std::function m_set_user_nativehandle_options; + std::function m_set_user_nativesessionhandle_options; ++ std::function m_nativehandle_servercertificate_validation; + + #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) + std::function m_ssl_context_callback; +diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp +index d6cdb5384a..5a517ec334 100644 +--- a/Release/src/http/client/http_client_winhttp.cpp ++++ b/Release/src/http/client/http_client_winhttp.cpp +@@ -2039,6 +2039,18 @@ class winhttp_client final : public _http_client_communicator + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + { + p_request_context->on_send_request_validate_cn(); ++ ++ try ++ { ++ p_request_context->m_http_client->client_config().invoke_nativehandle_servercertificate_validation(hRequestHandle); ++ } ++ catch (...) ++ { ++ p_request_context->report_exception(std::current_exception()); ++ p_request_context->cleanup(); ++ return; ++ } ++ + return; + } + case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: + diff --git a/src/VcpkgPortOverlay/patches/sfs-client/remove-unconditional-toolchain-override.patch b/src/VcpkgPortOverlay/patches/sfs-client/remove-unconditional-toolchain-override.patch new file mode 100644 index 0000000000..30115c567e --- /dev/null +++ b/src/VcpkgPortOverlay/patches/sfs-client/remove-unconditional-toolchain-override.patch @@ -0,0 +1,12 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -12,8 +12,6 @@ if(SFS_BUILD_TESTS) + list(APPEND VCPKG_MANIFEST_FEATURES "tests") + endif() + +-set(CMAKE_TOOLCHAIN_FILE "vcpkg/scripts/buildsystems/vcpkg.cmake") +- + # By default using x64 static custom triplet for Windows. Can be overridden by + # setting VCPKG_TARGET_TRIPLET + if(SFS_WINDOWS_STATIC_ONLY diff --git a/src/WindowsPackageManager/WindowsPackageManager.vcxproj b/src/WindowsPackageManager/WindowsPackageManager.vcxproj index 071e9e6f10..2d016963f2 100644 --- a/src/WindowsPackageManager/WindowsPackageManager.vcxproj +++ b/src/WindowsPackageManager/WindowsPackageManager.vcxproj @@ -427,9 +427,6 @@ {1cc41a9a-ae66-459d-9210-1e572dd7be69} - - {1b9077b3-8923-4ecd-8fc9-b3190fcbe4d4} - diff --git a/src/vcpkg.json b/src/vcpkg.json index 1e946182e6..8266468b0a 100644 --- a/src/vcpkg.json +++ b/src/vcpkg.json @@ -12,6 +12,7 @@ "dependencies": [ "catch2", "correlation-vector-cpp", + "sfs-client", { "name": "curl", "features": [ diff --git a/src/vcpkg.props b/src/vcpkg.props index a3b2750546..61470146a8 100644 --- a/src/vcpkg.props +++ b/src/vcpkg.props @@ -52,6 +52,13 @@ + + + + + From e6fce56a4ecf350561b3656b540280908f1ee9ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 13:26:20 -0700 Subject: [PATCH 09/18] Bump tmp from 0.2.5 to 0.2.7 in /tools/WinGetLogViewer (#6252) Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.5 to 0.2.7.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tmp&package-manager=npm_and_yarn&previous-version=0.2.5&new-version=0.2.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/microsoft/winget-cli/network/alerts).
###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/6252) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/WinGetLogViewer/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/WinGetLogViewer/package-lock.json b/tools/WinGetLogViewer/package-lock.json index 89ad2f4b3f..6c11b5c845 100644 --- a/tools/WinGetLogViewer/package-lock.json +++ b/tools/WinGetLogViewer/package-lock.json @@ -3703,9 +3703,9 @@ } }, "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", "engines": { From 22442d69e102cd6c6b5ec999807e8e39498db540 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 27 May 2026 17:11:28 -0500 Subject: [PATCH 10/18] Honor DSCv3 package installMode for silent and interactive (#6249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description When using DSCv3 Microsoft.WinGet/Package resource with installMode: silent specified, WinGet was not honoring the requested mode and instead defaulting to SilentWithProgress. This resulted in progress UI being shown and manifest custom switches being omitted from installer arguments. This PR maps the installMode property (declared in DscPackageResource) to execution context args (--silent or --interactive) before install/update/reinstall workflows execute, ensuring: - installMode: silent selects Silent installer switches - installMode: interactive selects Interactive installer switches - installMode: default preserves existing behavior (no mode arg added) - Manifest custom switches continue to be appended to installer arguments --- .github/actions/spelling/expect.txt | 1 + doc/ReleaseNotes.md | 1 + .../Commands/DscPackageResource.cpp | 14 ++++++ .../DSCv3PackageResourceCommand.cs | 48 +++++++++++++++++++ .../DSCv3ResourceTestBase.cs | 11 ++++- .../Helpers/TestCommon.cs | 45 ++++++++++++++--- .../InprocTestbedTests.cs | 2 +- src/AppInstallerTestExeInstaller/main.cpp | 31 ++++++++++-- 8 files changed, 139 insertions(+), 14 deletions(-) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 5940a5f446..6551758cbd 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -150,6 +150,7 @@ Dns Dobbeleer DONOT dsc +dupenv dustojnikhummer dvinns dwgs diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 60e8eda7bc..c9d54aadb8 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -86,3 +86,4 @@ Added a user setting (`logging.fileNameStrategy`) for controlling the default na * DSC export now correctly exports WinGet Admin Settings * `winget validate` now performs case-insensitive comparison for file extensions where applicable * `winget source reset` now properly resets default sources instead of removing them +* DSC v3 `Microsoft.WinGet/Package` resource now honors the `installMode` property to use silent or interactive installer switches as specified diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 7671af5210..69f9633c1e 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -89,6 +89,20 @@ namespace AppInstaller::CLI SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); SubContext->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); } + + std::string installMode = Utility::ToLower(Input.InstallMode().value_or("default")); + if (installMode == "silent") + { + SubContext->Args.AddArg(Execution::Args::Type::Silent); + } + else if (installMode == "interactive") + { + SubContext->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (installMode != "default") + { + THROW_HR(E_INVALIDARG); + } } void PrepareSubContextInputs() diff --git a/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs b/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs index 3a938b19a8..d371defb5a 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs @@ -7,6 +7,7 @@ namespace AppInstallerCLIE2ETests { using System.Collections.Generic; + using System.IO; using System.Text.Json.Serialization; using AppInstallerCLIE2ETests.Helpers; using NUnit.Framework; @@ -22,6 +23,7 @@ public class DSCv3PackageResourceCommand : DSCv3ResourceTestBase private const string DefaultPackageLowVersion = "1.0.0.0"; private const string DefaultPackageMidVersion = "1.1.0.0"; private const string DefaultPackageHighVersion = "2.0.0.0"; + private const string DefaultPackageInstallLocationEnvironmentVariableName = "WINGET_TEST_EXE_INSTALL_LOCATION"; private const string PackageResource = "package"; private const string VersionPropertyName = "version"; private const string UseLatestPropertyName = "useLatest"; @@ -303,6 +305,52 @@ public void Package_Set_SimpleRepeated() AssertDiffState(diff, []); } + /// + /// Calls `set` on the `package` resource with `installMode` set to `silent`. + /// + [Test] + public void Package_Set_SilentInstallMode_UsesSilentAndCustomSwitches() + { + string installDir = Path.GetTempPath(); + var environmentVariables = new Dictionary(); + environmentVariables[DefaultPackageInstallLocationEnvironmentVariableName] = installDir; + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + InstallMode = "silent", + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData, environmentVariables: environmentVariables); + AssertSuccessfulResourceRun(ref result); + + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/execustom")); + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/exesilent")); + TestCommon.BestEffortTestExeCleanup(installDir); + } + + /// + /// Calls `set` on the `package` resource with `installMode` set to `interactive`. + /// + [Test] + public void Package_Set_InteractiveInstallMode_UsesInteractiveAndCustomSwitches() + { + string installDir = Path.GetTempPath(); + var environmentVariables = new Dictionary(); + environmentVariables[DefaultPackageInstallLocationEnvironmentVariableName] = installDir; + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + InstallMode = "interactive", + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData, environmentVariables: environmentVariables); + AssertSuccessfulResourceRun(ref result); + + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/execustom")); + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/exeinteractive")); + TestCommon.BestEffortTestExeCleanup(installDir); + } + /// /// Calls `set` on the `package` resource to ensure that it is not present. /// diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs index d3f3d0d789..fe3b9e2ac4 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs @@ -69,10 +69,17 @@ public static void EnsureTestResourcePresence() /// Input for the function; supports null, direct string, or JSON serialization of complex objects. /// The maximum time to wait in milliseconds. /// Whether to throw on a timeout or simply return the incomplete result. + /// Environment variables to set. /// A RunCommandResult containing the process exit code and output and error streams. - protected static TestCommon.RunCommandResult RunDSCv3Command(string resource, string function, object input, int timeOut = 60000, bool throwOnTimeout = true) + protected static TestCommon.RunCommandResult RunDSCv3Command( + string resource, + string function, + object input, + int timeOut = 60000, + bool throwOnTimeout = true, + Dictionary environmentVariables = null) { - return TestCommon.RunAICLICommand($"dscv3 {resource}", $"--{function}", ConvertToJSON(input), timeOut, throwOnTimeout); + return TestCommon.RunAICLICommand($"dscv3 {resource}", $"--{function}", ConvertToJSON(input), timeOut, throwOnTimeout, environmentVariables); } /// diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 2b99865b68..6b3857fe39 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -7,6 +7,7 @@ namespace AppInstallerCLIE2ETests.Helpers { using System; + using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -112,8 +113,15 @@ public static bool IsCIEnvironment /// Optional std in. /// Optional timeout. /// Throw on timeout. + /// Environment variables to set. /// The result of the command. - public static RunCommandResult RunAICLICommand(string command, string parameters, string stdIn = null, int timeOut = 60000, bool throwOnTimeout = true) + public static RunCommandResult RunAICLICommand( + string command, + string parameters, + string stdIn = null, + int timeOut = 60000, + bool throwOnTimeout = true, + Dictionary environmentVariables = null) { string correlationParameter = " --correlation " + Guid.NewGuid().ToString(); @@ -126,7 +134,7 @@ public static RunCommandResult RunAICLICommand(string command, string parameters } } - return RunAICLICommandViaDirectProcess(command, parameters + correlationParameter, stdIn, timeOut, throwOnTimeout); + return RunAICLICommandViaDirectProcess(command, parameters + correlationParameter, stdIn, timeOut, throwOnTimeout, environmentVariables); } /// @@ -1159,15 +1167,25 @@ public static string CopyInstallerFileToARPInstallSourceDirectory(string install /// Optional std in. /// Optional timeout. /// Throw on timeout. + /// Environment variables to set. /// The result of the command. - public static RunCommandResult RunProcess(string executablePath, string command, string parameters, string stdIn, int timeOut, bool throwOnTimeout) + public static RunCommandResult RunProcess( + string executablePath, + string command, + string parameters, + string stdIn, + int timeOut, + bool throwOnTimeout, + Dictionary environmentVariables) { string inputMsg = "Exe path: " + executablePath + " Command: " + command + " Parameters: " + parameters + (string.IsNullOrEmpty(stdIn) ? string.Empty : " StdIn: " + stdIn) + - " Timeout: " + timeOut; + " Timeout: " + timeOut + + (environmentVariables == null ? string.Empty : + " Env: " + string.Join(", ", environmentVariables.Select(item => $"{item.Key}={item.Value}"))); TestContext.Out.WriteLine($"Starting command run. {inputMsg}"); @@ -1203,6 +1221,14 @@ public static RunCommandResult RunProcess(string executablePath, string command, p.StartInfo.RedirectStandardInput = true; } + if (environmentVariables != null) + { + foreach (var item in environmentVariables) + { + p.StartInfo.EnvironmentVariables[item.Key] = item.Value; + } + } + p.Start(); p.BeginOutputReadLine(); p.BeginErrorReadLine(); @@ -1251,10 +1277,17 @@ public static RunCommandResult RunProcess(string executablePath, string command, /// Optional std in. /// Optional timeout. /// Throw on timeout. + /// Environment variables to set. /// The result of the command. - private static RunCommandResult RunAICLICommandViaDirectProcess(string command, string parameters, string stdIn, int timeOut, bool throwOnTimeout) + private static RunCommandResult RunAICLICommandViaDirectProcess( + string command, + string parameters, + string stdIn, + int timeOut, + bool throwOnTimeout, + Dictionary environmentVariables) { - return RunProcess(TestSetup.Parameters.AICLIPath, command, parameters, stdIn, timeOut, throwOnTimeout); + return RunProcess(TestSetup.Parameters.AICLIPath, command, parameters, stdIn, timeOut, throwOnTimeout, environmentVariables); } /// diff --git a/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs index 29104e7bb1..229b6c2d37 100644 --- a/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs +++ b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs @@ -212,7 +212,7 @@ private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000 builtParameters += $"-no-term "; } - var result = TestCommon.RunProcess(this.InprocTestbedPath, this.TargetPackageInformation, builtParameters, null, timeout, true); + var result = TestCommon.RunProcess(this.InprocTestbedPath, this.TargetPackageInformation, builtParameters, null, timeout, true, null); Assert.AreEqual(0, result.ExitCode); } diff --git a/src/AppInstallerTestExeInstaller/main.cpp b/src/AppInstallerTestExeInstaller/main.cpp index cd93e3618f..b5b7bc9a11 100644 --- a/src/AppInstallerTestExeInstaller/main.cpp +++ b/src/AppInstallerTestExeInstaller/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include using namespace std::filesystem; @@ -17,6 +18,7 @@ std::wstring_view DefaultProductID = L"{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}"; std::wstring_view DefaultDisplayName = L"AppInstallerTestExeInstaller"; std::wstring_view DefaultDisplayVersion = L"1.0.0.0"; std::wstring_view DscSubDirectoryName = L"SubDirectory"; +auto InstallLocationEnvironmentVariableName = "WINGET_TEST_EXE_INSTALL_LOCATION"; void WriteModifyRepairScript(std::wofstream& script, const path& repairCompletedTextFilePath, bool isModifyScript) { std::wstring scriptName = isModifyScript ? L"Modify" : L"Uninstaller"; @@ -447,7 +449,7 @@ void HandleInstallationOperation( // The installer prints all args to an output file and writes to the Uninstall registry key int wmain(int argc, const wchar_t** argv) { - path installDirectory = temp_directory_path(); + std::optional installDirectory; std::wstringstream outContent; std::wstring productCode; std::wstring displayName; @@ -476,7 +478,6 @@ int wmain(int argc, const wchar_t** argv) if (++i < argc) { installDirectory = argv[i]; - std::filesystem::create_directories(installDirectory); outContent << argv[i] << ' '; } } @@ -616,6 +617,28 @@ int wmain(int argc, const wchar_t** argv) } } + if (!installDirectory) + { + char* value = nullptr; + size_t valueLength = 0; + errno_t result = _dupenv_s(&value, &valueLength, InstallLocationEnvironmentVariableName); + if (result == 0 && value) + { + installDirectory = value; + } + else + { + installDirectory = temp_directory_path(); + } + + if (value) + { + free(value); + } + } + + std::filesystem::create_directories(installDirectory.value()); + if (noOperation) { return exitCode; @@ -650,8 +673,6 @@ int wmain(int argc, const wchar_t** argv) displayVersion = DefaultDisplayVersion; } - path outFilePath = installDirectory; - if (isRepair) { outContent << L"\nInstaller Repair operation for AppInstallerTestExeInstaller.exe completed successfully."; @@ -659,7 +680,7 @@ int wmain(int argc, const wchar_t** argv) } else { - HandleInstallationOperation(*out, installDirectory, outContent, productCode, useHKLM, displayName, displayVersion, noRepair, noModify, generateDscResourceFiles); + HandleInstallationOperation(*out, installDirectory.value(), outContent, productCode, useHKLM, displayName, displayVersion, noRepair, noModify, generateDscResourceFiles); } return exitCode; From f1eb8160c8474ebfa8c5473ba55d1bec911e9dec Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 28 May 2026 13:04:31 -0500 Subject: [PATCH 11/18] Fix triage label getting removed (#6254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description Although these three labels get applied when the issue is opened, it is technically a separate event that happens *after* the the issue is opened. This means that the policy service is catching this event and removing issues from triage before they are actually triaged. This is a faulty implementation from #6202 ## 🔗 References See recently opened issue https://github.com/microsoft/winget-cli/issues/6245 ## 🔍 Validation None ## ✅ Checklist - [x] Signed the [Contributor License Agreement](https://cla.opensource.microsoft.com) - [ ] Linked to an issue - [ ] Updated [Release Notes](../doc/ReleaseNotes.md) (if applicable) - [ ] Updated documentation (if applicable) - [ ] Updated [Copilot instructions](.github/copilot-instructions.md) (if build, architecture, or conventions changed) ## 📋 Issue Type - [x] Bug fix - [ ] Feature - [ ] Task ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/6254) --- .github/policies/labelManagement.triageLabels.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/policies/labelManagement.triageLabels.yml b/.github/policies/labelManagement.triageLabels.yml index 19231ca052..e0e2461d30 100644 --- a/.github/policies/labelManagement.triageLabels.yml +++ b/.github/policies/labelManagement.triageLabels.yml @@ -66,12 +66,6 @@ configuration: label: In-PR - labelAdded: label: Interactive-Only-Installer - - labelAdded: - label: Issue-Bug - - labelAdded: - label: Issue-Docs - - labelAdded: - label: Issue-Feature - labelAdded: label: msstore - labelAdded: From 9ab36113bd91175f16d7bbbce4f4c8bac0708df8 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 28 May 2026 11:34:05 -0700 Subject: [PATCH 12/18] lowercase ARM64 for vcpkg (#6256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📖 Description Force the vcpkg triplet platform to be lowercase for `ARM64`. --- src/vcpkg.props | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vcpkg.props b/src/vcpkg.props index 61470146a8..93e403e0d6 100644 --- a/src/vcpkg.props +++ b/src/vcpkg.props @@ -3,9 +3,11 @@ true $(MSBuildThisFileDirectory)vcpkg_installed\ - + <_VcpkgTripletPlatform>$(Platform) <_VcpkgTripletPlatform Condition="'$(Platform)'=='Win32'">x86 + <_VcpkgTripletPlatform Condition="'$(Platform)'=='ARM64'">arm64 $(_VcpkgTripletPlatform) From 2f4f2cf16806debffc6c9d7e724b5c900f455a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:34:19 -0700 Subject: [PATCH 13/18] Apply latest loc patch (#6262) ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/6262) --- .../Policies/de-DE/DesktopAppInstaller.adml | 9 + .../Policies/es-ES/DesktopAppInstaller.adml | 9 + .../Policies/fr-FR/DesktopAppInstaller.adml | 9 + .../Policies/it-IT/DesktopAppInstaller.adml | 9 + .../Policies/ja-JP/DesktopAppInstaller.adml | 9 + .../Policies/ko-KR/DesktopAppInstaller.adml | 9 + .../Policies/pt-BR/DesktopAppInstaller.adml | 9 + .../Policies/ru-RU/DesktopAppInstaller.adml | 9 + .../Policies/zh-CN/DesktopAppInstaller.adml | 9 + .../Policies/zh-TW/DesktopAppInstaller.adml | 9 + Localization/Resources/de-DE/winget.resw | 162 ++++++++++++++--- Localization/Resources/es-ES/winget.resw | 158 +++++++++++++--- Localization/Resources/fr-FR/winget.resw | 164 ++++++++++++++--- Localization/Resources/it-IT/winget.resw | 168 ++++++++++++++--- Localization/Resources/ja-JP/winget.resw | 170 +++++++++++++++--- Localization/Resources/ko-KR/winget.resw | 166 ++++++++++++++--- Localization/Resources/pt-BR/winget.resw | 164 ++++++++++++++--- Localization/Resources/ru-RU/winget.resw | 170 +++++++++++++++--- Localization/Resources/zh-CN/winget.resw | 164 ++++++++++++++--- Localization/Resources/zh-TW/winget.resw | 170 +++++++++++++++--- 20 files changed, 1488 insertions(+), 258 deletions(-) diff --git a/Localization/Policies/de-DE/DesktopAppInstaller.adml b/Localization/Policies/de-DE/DesktopAppInstaller.adml index 2caa84cadb..99fb768a78 100644 --- a/Localization/Policies/de-DE/DesktopAppInstaller.adml +++ b/Localization/Policies/de-DE/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ Wenn Sie diese Einstellung deaktivieren oder nicht konfigurieren, können Benutz Wenn Sie diese Einstellung aktivieren oder nicht konfigurieren, können Benutzer den Windows-Paket-Manager verwenden. Wenn Sie diese Einstellung deaktivieren, können Benutzer den Windows-Paket-Manager nicht verwenden. + Außerkraftsetzung des Konfigurationsprozessors für den Windows-Paket-Manager aktivieren + + Diese Richtlinie steuert, ob Benutzer einen benutzerdefinierten DSC-Prozessorpfad über das Argument "--processor-path" in Konfigurationsbefehlen des Windows-Paket-Manager angeben können. + +Wenn Sie diese Einstellung aktivieren, können Benutzer in Konfigurationsbefehlen einen benutzerdefinierten DSC-Prozessorpfad angeben. + +Wenn Sie diese Einstellung nicht konfigurieren, können Benutzer in den Konfigurationsbefehlen einen benutzerdefinierten DSC-Prozessorpfad angeben, nachdem sie die zugehörige Administratoreinstellung aktiviert haben. + +Wenn Sie diese Einstellung deaktivieren, können Benutzer keinen benutzerdefinierten DSC-Prozessorpfad in Konfigurationsbefehlen angeben. Standardproxy für den Windows-Paket-Manager festlegen Diese Richtlinie steuert den Standardproxy, der vom Windows-Paket-Manager verwendet wird. diff --git a/Localization/Policies/es-ES/DesktopAppInstaller.adml b/Localization/Policies/es-ES/DesktopAppInstaller.adml index 540046238c..dd038930c9 100644 --- a/Localization/Policies/es-ES/DesktopAppInstaller.adml +++ b/Localization/Policies/es-ES/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ Si deshabilita o no establece esta configuración, los usuarios no podrán confi Si habilita o no establece esta configuración, los usuarios podrán usar el servidor MCP del Administrador de paquetes de Windows. Si deshabilita esta configuración, los usuarios no podrán usar el servidor MCP del Administrador de paquetes de Windows. + Habilitar invalidación del procesador de configuración de Administrador de paquetes de Windows + + Esta directiva controla si los usuarios pueden especificar una ruta de acceso de procesador DSC personalizada mediante el argumento --processor-path en los comandos de configuración del Administrador de paquetes de Windows. + +Si habilita esta opción, los usuarios podrán especificar una ruta de acceso del procesador DSC personalizada en los comandos de configuración. + +Si no establece esta configuración, los usuarios podrán especificar una ruta de acceso de procesador DSC personalizada en los comandos de configuración después de habilitar la configuración de administrador relacionada. + +Si deshabilita esta opción, los usuarios no podrán especificar una ruta de acceso del procesador DSC personalizada en los comandos de configuración. Establecer Administrador de paquetes de Windows proxy predeterminado Esta directiva controla el proxy predeterminado que usa el Administrador de paquetes de Windows. diff --git a/Localization/Policies/fr-FR/DesktopAppInstaller.adml b/Localization/Policies/fr-FR/DesktopAppInstaller.adml index cc115acd51..a7dd8b421d 100644 --- a/Localization/Policies/fr-FR/DesktopAppInstaller.adml +++ b/Localization/Policies/fr-FR/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ Si vous désactivez ou ne configurez pas ce paramètre, les utilisateurs ne peuv Si vous activez ou ne configurez pas ce paramètre, les utilisateurs peuvent utiliser le serveur MCP du Gestionnaire de package Windows. Si vous désactivez ce paramètre, les utilisateurs ne peuvent pas utiliser le serveur MCP du Gestionnaire de package Windows. + Activer le remplacement du processeur de configuration du Gestionnaire de package Windows + + Cette stratégie contrôle si les utilisateurs peuvent spécifier un chemin d’accès de processeur DSC personnalisé via l’argument --processor-path dans les commandes de configuration du Gestionnaire de package Windows. + +Si vous activez ce paramètre, les utilisateurs peuvent spécifier un chemin d’accès au processeur DSC personnalisé dans les commandes de configuration. + +Si vous ne configurez pas ce paramètre, les utilisateurs peuvent spécifier un chemin d’accès au processeur DSC personnalisé dans les commandes de configuration après avoir activé le paramètre administrateur associé. + +Si vous désactivez ce paramètre, les utilisateurs ne peuvent pas spécifier de chemin d’accès au processeur DSC personnalisé dans les commandes de configuration. Définir Gestionnaire de package Windows proxy par défaut Cette stratégie contrôle le proxy par défaut utilisé par le Gestionnaire de package Windows. diff --git a/Localization/Policies/it-IT/DesktopAppInstaller.adml b/Localization/Policies/it-IT/DesktopAppInstaller.adml index 1a53ce3301..62f72b886e 100644 --- a/Localization/Policies/it-IT/DesktopAppInstaller.adml +++ b/Localization/Policies/it-IT/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ Se si disabilita o non si configura questa impostazione, gli utenti non potranno Se si abilita o non si configura questa impostazione, gli utenti potranno utilizzare il server MCP del Gestione pacchetti Windows. Se si disabilita questa impostazione, gli utenti non potranno utilizzare il server MCP del Gestione pacchetti Windows. + Abilita l'override del processore di configurazione di Gestione pacchetti Windows + + Questo criterio controlla se gli utenti possono specificare un percorso del processore DSC personalizzato tramite l'argomento --processor-path nei comandi di configurazione di Gestione pacchetti Windows. + +Se si abilita questa impostazione, gli utenti potranno specificare un percorso del processore DSC personalizzato nei comandi di configurazione. + +Se non si configura questa impostazione, gli utenti potranno specificare un percorso del processore DSC personalizzato nei comandi di configurazione dopo aver abilitato l'impostazione amministratore correlata. + +Se si disabilita questa impostazione, gli utenti non potranno specificare un percorso del processore DSC personalizzato nei comandi di configurazione. Imposta proxy predefinito Gestione pacchetti Windows Questo criterio controlla il proxy predefinito utilizzato dal Gestione pacchetti Windows. diff --git a/Localization/Policies/ja-JP/DesktopAppInstaller.adml b/Localization/Policies/ja-JP/DesktopAppInstaller.adml index f9b7dedb73..fec41e93c3 100644 --- a/Localization/Policies/ja-JP/DesktopAppInstaller.adml +++ b/Localization/Policies/ja-JP/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ この設定を有効にするか、未構成にした場合、ユーザーはWindows パッケージ マネージャーの MCP サーバーを使用できます。 この設定を無効にすると、ユーザーはWindows パッケージ マネージャーの MCP サーバーを使用できなくなります。 + Windows パッケージ マネージャー構成プロセッサーのオーバーライドを有効にします + + このポリシーは、ユーザーが Windows パッケージ マネージャー構成コマンドの --processor-path 引数を使用してカスタム DSC プロセッサ パスを指定できるかどうかを制御します。 + +この設定を有効にした場合、ユーザーは構成コマンドでカスタム DSC プロセッサ パスを指定できます。 + +この設定を構成しなかった場合、関連する管理者設定を有効にすると、ユーザーは構成コマンドでカスタム DSC プロセッサ パスを指定できるようになります。 + +この設定を無効にした場合、ユーザーは構成コマンドでカスタム DSC プロセッサ パスを指定できなくなります。 Windows パッケージ マネージャーの既定のプロキシを設定する このポリシーは、Windows パッケージ マネージャーで使用される既定のプロキシを制御します。 diff --git a/Localization/Policies/ko-KR/DesktopAppInstaller.adml b/Localization/Policies/ko-KR/DesktopAppInstaller.adml index e2ba7c4eea..ca44492cc3 100644 --- a/Localization/Policies/ko-KR/DesktopAppInstaller.adml +++ b/Localization/Policies/ko-KR/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ 이 설정을 사용하거나 구성하지 않으면 사용자가 Windows 패키지 관리자 MCP 서버를 사용할 수 있습니다. 이 설정을 사용하지 않으면 사용자가 Windows 패키지 관리자 MCP 서버를 사용할 수 없습니다. + Windows 패키지 관리자 구성 프로세서 재정의 사용 + + 이 정책은 사용자가 Windows 패키지 관리자 구성 명령의 --processor-path 인수를 통해 사용자 지정 DSC 프로세서 경로를 지정할 수 있는지 여부를 제어합니다. + +이 설정을 사용하면 사용자가 구성 명령에서 사용자 지정 DSC 프로세서 경로를 지정할 수 있습니다. + +이 설정을 구성하지 않으면 사용자는 관련 관리자 설정을 사용하도록 설정한 후 구성 명령에서 사용자 지정 DSC 프로세서 경로를 지정할 수 있습니다. + +이 설정을 사용하지 않으면 사용자가 구성 명령에서 사용자 지정 DSC 프로세서 경로를 지정할 수 없습니다. Windows 패키지 관리자 기본 프록시 설정 이 정책은 Windows 패키지 관리자 사용하는 기본 프록시를 제어합니다. diff --git a/Localization/Policies/pt-BR/DesktopAppInstaller.adml b/Localization/Policies/pt-BR/DesktopAppInstaller.adml index 1c78f6f91b..b53ebc620c 100644 --- a/Localization/Policies/pt-BR/DesktopAppInstaller.adml +++ b/Localization/Policies/pt-BR/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ Se você desabilitar ou não definir essa configuração, os usuários não pode Se você habilitar ou não definir essa configuração, os usuários poderão usar o Gerenciador de Pacotes do Windows MCP do servidor. Se você desabilitar essa configuração, os usuários não poderão usar o Gerenciador de Pacotes do Windows MCP do servidor. + Habilitar Substituição do Processador de Configuração do Gerenciador de Pacotes do Windows + + Esta política controla se os usuários podem especificar um caminho de processador DSC personalizado por meio do argumento --processor-path nos comandos de configuração do Gerenciador de Pacotes do Windows. + +Se você habilitar essa configuração, os usuários poderão especificar um caminho de processador DSC personalizado nos comandos de configuração. + +Se você não definir essa configuração, os usuários poderão especificar um caminho personalizado do processador DSC nos comandos de configuração depois de habilitar a configuração de administrador relacionada. + +Se você desabilitar essa configuração, os usuários não poderão especificar um caminho de processador DSC personalizado nos comandos de configuração. Definir Proxy Padrão do Gerenciador de Pacotes do Windows Esta política controla o proxy padrão usado pelo Gerenciador de Pacotes do Windows. diff --git a/Localization/Policies/ru-RU/DesktopAppInstaller.adml b/Localization/Policies/ru-RU/DesktopAppInstaller.adml index 54aa1a65da..79e5996f7b 100644 --- a/Localization/Policies/ru-RU/DesktopAppInstaller.adml +++ b/Localization/Policies/ru-RU/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ Если этот параметр включен или не настроен, пользователи смогут применять сервер MCP Диспетчера пакетов Windows. Если этот параметр отключен, пользователи не смогут применять сервер MCP Диспетчера пакетов Windows. + Включить переопределение обработчика конфигурации Диспетчера пакетов Windows + + Эта политика определяет, могут ли пользователи указывать настраиваемый путь к процессору DSC с помощью аргумента --processor-path в командах конфигурации Диспетчера пакетов Windows. + + Если включить этот параметр, пользователи смогут указывать настраиваемый путь к процессору DSC в командах конфигурации. + + Если этот параметр не настроен, пользователи смогут указывать настраиваемый путь к процессору DSC в командах конфигурации после включения параметра администратора, связанного с данным параметром. + + Если отключить этот параметр, пользователи не смогут указывать настраиваемый путь к процессору DSC в командах конфигурации. Задать прокси по умолчанию для Диспетчера пакетов Windows Эта политика управляет прокси-сервером по умолчанию, используемым Диспетчер пакетов Windows. diff --git a/Localization/Policies/zh-CN/DesktopAppInstaller.adml b/Localization/Policies/zh-CN/DesktopAppInstaller.adml index 7c289ded55..ca10c35ce0 100644 --- a/Localization/Policies/zh-CN/DesktopAppInstaller.adml +++ b/Localization/Policies/zh-CN/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ 如果启用或未配置此设置,则用户将能够使用Windows 程序包管理器的 MCP 服务器。 如果禁用此设置,则用户将无法使用Windows 程序包管理器的 MCP 服务器。 + 启用 Windows 程序包管理器配置处理器替代 + + 此策略控制用户是否可以通过 Windows 程序包管理器配置命令中的 --processor-path 参数指定自定义 DSC 处理器路径。 + +如果启用此设置,用户将能够在配置命令中指定自定义 DSC 处理器路径。 + +如果未配置此设置,则在启用相关管理员设置后,用户将能够在配置命令中指定自定义 DSC 处理器路径。 + +如果禁用此设置,用户将无法在配置命令中指定自定义 DSC 处理器路径。 设置 Windows 程序包管理器默认代理 此策略控制Windows 程序包管理器使用的默认代理。 diff --git a/Localization/Policies/zh-TW/DesktopAppInstaller.adml b/Localization/Policies/zh-TW/DesktopAppInstaller.adml index 69f00954fd..2166b316bc 100644 --- a/Localization/Policies/zh-TW/DesktopAppInstaller.adml +++ b/Localization/Policies/zh-TW/DesktopAppInstaller.adml @@ -133,6 +133,15 @@ 如果您啟用或未設定此原則,使用者將能使用 Windows 封裝管理員的 MCP 伺服器。 如果您停用此原則,使用者將無法使用 Windows 封裝管理員的 MCP 伺服器。 + 啟用 Windows 套件管理員設定處理器覆寫 + + 此原則控制使用者是否可以透過 Windows 封裝管理員設定命令中的 --processor-path 引數指定自訂 DSC 處理器路徑。 + +如果您啟用這個設定,使用者將可以在設定命令中指定自訂 DSC 處理器路徑。 + +如果您未設定這個設定,啟用相關的系統管理員設定之後,使用者將可以在設定命令中指定自訂 DSC 處理器路徑。 + +如果您停用這個設定,使用者將無法在組態命令中指定自訂 DSC 處理器路徑。 設定 Windows 封裝管理員預設 Proxy 此原則控制Windows 封裝管理員使用的預設 Proxy。 diff --git a/Localization/Resources/de-DE/winget.resw b/Localization/Resources/de-DE/winget.resw index 335284e0f5..38fe8c57e1 100644 --- a/Localization/Resources/de-DE/winget.resw +++ b/Localization/Resources/de-DE/winget.resw @@ -214,15 +214,19 @@ Deaktiviert + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. Aktiviert + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. Funktion + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. Link + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. Die folgenden experimentellen Features werden zurzeit ausgeführt. @@ -231,9 +235,11 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Eigenschaft + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. Status + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. Zu hashende Datei @@ -271,8 +277,8 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Microsoft ist nicht verantwortlich und erteilt keine Lizenzen für Pakete von Drittanbietern. - Dieses Paket wird über den Microsoft Store bereitgestellt. “winget” muss das Paket möglicherweise im Auftrag des aktuellen Benutzers aus dem Microsoft Store abrufen. - {Locked="winget"} + Dieses Paket wird über den Microsoft Store bereitgestellt. WinGet muss das Paket möglicherweise im Auftrag des aktuellen Benutzers aus dem Microsoft Store abrufen. + {Locked="WinGet"} Installiert das ausgewählte Paket, entweder durch Durchsuchen einer konfigurierten Quelle oder direkt aus einem Manifest. Standardmäßig muss die Abfrage ohne Berücksichtigung der Groß- und Kleinschreibung mit id, name oder moniker des Pakets übereinstimmen. Andere Felder können verwendet werden, indem die entsprechende Option übergeben wird. Standardmäßig überprüft der Installationsbefehl den Installationsstatus des Pakets und versucht allenfalls ein Upgrade durchzuführen. Setzen Sie dies mit „--force“ außer Kraft, um eine direkte Installation auszuführen. @@ -324,7 +330,7 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde WinGet-Verzeichnisse - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs Zu verwendendes Gebietsschema (BCP47-Format) @@ -351,7 +357,7 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Log-Speicherort (sofern unterstützt) - Copyright (c) Microsoft Corporation. Alle Rechte vorbehalten. + © 2026 Microsoft. Alle Rechte vorbehalten. Startseite @@ -468,12 +474,14 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Übereinstimmung + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. Name Quelle + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. zusätzliche Einträge wegen Ergebnisgrenzwert abgeschnitten @@ -497,6 +505,7 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Kanal + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. Zeigt Informationen zu einem bestimmten Paket an. Standardmäßig muss die Abfrage ohne Berücksichtigung der Groß-/Kleinschreibung mit der ID, dem Namen oder dem Moniker des Pakets übereinstimmen. Andere Felder können verwendet werden, indem Sie Ihre entsprechende Option übergeben. @@ -515,6 +524,18 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Auf einen einzelnen Bindestrich muss ein Alias aus einem einzelnen Zeichen folgen: „{0}“ {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + Ergebnisse nach einer Eigenschaft sortieren (kann wiederholt werden) + Description for the --sort argument used to sort output by field name. + + + Ergebnisse in aufsteigender Reihenfolge sortieren + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Ergebnisse in absteigender Reihenfolge sortieren + Description for the --descending (--desc) flag that sets descending sort direction for output. + Eine Quelle mit dem angegebenen Namen ist bereits vorhanden und verweist auf einen anderen Speicherort: @@ -616,8 +637,8 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde {Locked="source reset"} - Fehler beim Öffnen der vordefinierten Quelle. Bitte melden Sie dieses Problem an die Entwickler von winget. - {Locked="winget"} + Fehler beim Öffnen der vordefinierten Quelle. Melden Sie dies den WinGet-Verantwortlichen. + {Locked="WinGet"} Alle Quellen werden entfernt... @@ -672,7 +693,8 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Ergebnisse nach Bezeichnung filtern - Vielen Dank, dass Sie WinGet nutzen + Vielen Dank für die Nutzung von WinGet + {Locked="WinGet"} Hinweise von Drittanbietern @@ -771,6 +793,7 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Gefunden + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. Pfad ist ein Verzeichnis: {0} @@ -814,8 +837,8 @@ Sie können über die Einstellungsdatei „winget settings“ konfiguriert werde Erfolgreich deinstalliert - Das „winget“-Tool kann den Deinstallationsbefehl für dieses Paket nicht finden. Bitte wenden Sie sich an den Herausgeber des Pakets, um Unterstützung zu erhalten. - {Locked="winget"} + WinGet kann den Deinstallationsbefehl für dieses Paket nicht finden. Wenden Sie sich für Unterstützung an den Herausgeber des Pakets. + {Locked="WinGet"} Deinstallation abgebrochen @@ -998,6 +1021,7 @@ Die Konfiguration ist aufgrund der Gruppenrichtlinie deaktiviert. Extern + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". Die Pakete, die in diesem Import gefunden wurden, weisen die folgenden Abhängigkeiten auf: @@ -1344,6 +1368,10 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Es wurde eine neuere Version gefunden, die Installationstechnologie unterscheidet sich jedoch von der aktuellen installierten Version. Deinstallieren Sie das Paket, und installieren Sie die neuere Version. + + {0} Pakete sind Upgrades blockiert, da neuere Versionen eine andere Installationstechnologie als die aktuelle Installation verwenden. Deinstallieren Sie die einzelnen Pakete, und installieren Sie dann die neuere Version. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + Die Installationstechnologie der angegebenen neueren Version unterscheidet sich von der aktuell installierten Version. Deinstallieren Sie das Paket, und installieren Sie die neuere Version. @@ -1462,6 +1490,7 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Installationsverzeichnis kann nicht gelöscht werden, da es nicht von WinGet erstellt wurde + {Locked="WinGet"} Verwandter Link @@ -1724,7 +1753,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - Das Paket kann nicht mit winget aktualisiert werden. Verwenden Sie die vom Herausgeber bereitgestellte Methode zum Aktualisieren dieses Pakets. + Das Paket kann nicht mit WinGet aktualisiert werden. Verwenden Sie die vom Herausgeber bereitgestellte Methode zum Aktualisieren dieses Pakets. + {Locked="WinGet"} Aktualisieren von Paketen auch dann, wenn sie über einen nicht blockierenden Pin verfügen @@ -2021,6 +2051,12 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Überspringt die Verarbeitung von Paketabhängigkeiten und Windows-Features. + + Installiert nur die Abhängigkeiten des Pakets + + + Es werden nur Abhängigkeiten installiert. Das Paket selbst wird nicht installiert. + Abhängigkeiten übersprungen. @@ -2111,7 +2147,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Windows-Paket-Manager-Konfiguration aktivieren - Hiermit werden Informationen zu Fehlern abgerufen. Bei Angabe einer Zahl enthält die Ausgabe Details zum Fehler, einschließlich des Symbolnamens, wenn es sich um einen wingetspezifischen Fehler handelt. Bei Angabe einer Zeichenfolge werden die wingetspezifischen Fehler nach diesem Wert durchsucht. + Informationen zu Fehlern abrufen. Bei einer Zahl enthält die Ausgabe Details zum Fehler, einschließlich des Symbolnamens, wenn es sich um einen WinGet spezifischen Fehler handelt. Anhand einer Zeichenfolge werden die WinGet spezifischen Fehler nach diesem Wert durchsucht. + {Locked="WinGet"} Informationen zu Fehlern abrufen @@ -2119,6 +2156,18 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Ein Wert, der in den Fehlerinformationen gesucht werden soll + + Markdowndatei mit Fehlercodes generieren + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + Das --output-Argument kann nicht mit einem Eingabewert kombiniert werden. + {Locked="--output"} + + + Ein Eingabewert oder --output muss angegeben werden. + {Locked="--output"} + Die angegebene Zahl ist zu groß, um ein HRESULT zu sein. @@ -2210,7 +2259,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Die Microsoft Store-App wird durch eine Richtlinie blockiert - Das Feature befindet sich zurzeit in der Entwicklung. Sie kann mithilfe von Wingeteinstellungen aktiviert werden. + Das Feature befindet sich derzeit in der Entwicklung. Sie kann mit "winget settings" aktiviert werden. + {Locked="winget settings"} Fehler beim Installieren der Microsoft Store-App @@ -2254,6 +2304,9 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Es wurden keine anwendbaren Aktualisierungen gefunden + + Ein Upgrade ist verfügbar, verwendet aber eine andere Installationstechnologie als die aktuelle Installation. + winget-Upgrade – vollständig mit Fehlern abgeschlossen @@ -2780,8 +2833,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Geben Sie das Konto an, das für die Authentifizierung verwendet werden soll - Fehler beim Hinzufügen der Quelle. Diese winget Version unterstützt die Authentifizierungsmethode der Quelle nicht. Versuchen Sie, ein Upgrade auf die neueste winget Version durchzuführen. - {Locked="winget"} + Fehler beim Hinzufügen der Quelle. Diese WinGet Version unterstützt die Authentifizierungsmethode der Quelle nicht. Führen Sie ein Upgrade auf die neueste WinGet Version durch. + {Locked="WinGet"} Für die {0} Quelle ist eine Authentifizierung erforderlich. Bei Bedarf wird möglicherweise eine Authentifizierungsaufforderung angezeigt. Authentifizierte Informationen werden für die Zugriffsautorisierung mit der Quelle geteilt. @@ -2861,12 +2914,19 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Befehlszeilenoptionen für Windows-Paket-Manager Proxy aktivieren Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Konfigurationsprozessorpfad des Windows-Paket-Managers aktivieren + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + Legen Sie einen Proxy fest, der für diese Ausführung verwendet werden soll. Verwendung des Proxys für diese Ausführung deaktivieren + + Deaktiviert die Statusanzeige und den Wartekreisel + {0} kann nicht zurückgesetzt werden. Diese Einstellung wird durch eine Richtlinie gesteuert. Wenden Sie sich an den Systemadministrator, um weitere Informationen zu erfahren. {Locked="{0}"} The value will be replaced with the feature name @@ -2908,7 +2968,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Schließt eine Quelle aus der Ermittlung aus (TRUE oder FALSE). - Anstößig + Explizit + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. Vertrauensebene der Quelle (keine oder vertrauenswürdig) @@ -3184,17 +3245,19 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Familie + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - Gesichter - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + Schriftarten + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - Ergebnisse nach Familienname filtern + Ergebnisse nach Schriftart filtern + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - Gesicht - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + Schriftart + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. Pfade @@ -3219,8 +3282,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Das Paketinstallationsprogramm erfordert eine Authentifizierung. Bei Bedarf wird möglicherweise eine Authentifizierungsaufforderung angezeigt. Authentifizierte Informationen werden mit der Download-URL des Installers geteilt. - Fehler beim Herunterladen des Installers. Diese winget Version unterstützt die Downloadauthentifizierungsmethode des Installers nicht. Versuchen Sie, ein Upgrade auf die neueste winget Version durchzuführen. - {Locked="winget"} + Fehler beim Herunterladen des Installationsprogramms. Diese WinGet Version unterstützt die Authentifizierungsmethode für den Download des Installationsprogramms nicht. Führen Sie ein Upgrade auf die neueste WinGet Version durch. + {Locked="WinGet"} Fehler beim Herunterladen des Installationsprogramms. Fehler bei der Authentifizierung. @@ -3228,12 +3291,41 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Pfad zum Konfigurationsprozessor angeben + + Benutzerdefinierter Prozessorpfad: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hash: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Typ: App-Ausführungsalias + Indicates the processor path is an app execution alias (MSIX package link). + + + Pfad: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Signiert von: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Signiert von: (nicht signiert) + Indicates the custom processor executable does not have an Authenticode signature. + + + Fehler bei der Integritätsprüfung für den benutzerdefinierten DSC-Prozessorpfad. Der Pfad wurde möglicherweise geändert. + Error when the server-side hash of the processor path does not match the client-computed hash. + DSC v3-Ressourcenbefehle DSC stands for "Desired State Configuration". It should already have a locked translation. Die untergeordneten Befehle implementieren Desired State Configuration (DSC) v3-Ressourcen zur Konfiguration von WinGet und Paketen. + {Locked="WinGet"} Abrufen des Ressourcenstatus @@ -3272,7 +3364,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Paketstatus verwalten - Pakete über WinGet verwalten. + Pakete mit WinGet verwalten. + {Locked="WinGet"} Gibt an, ob eine Instanz vorhanden oder vorhanden sein soll. @@ -3309,6 +3402,7 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Verwalten Sie die Benutzereinstellungen von WinGet. + {Locked="WinGet"} Der Inhalt der JSON-Einstellungsdatei. @@ -3324,6 +3418,7 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Verwalten Sie die Quellen von WinGet. + {Locked="WinGet"} Der Name, der für die Quelle verwendet werden soll. @@ -3383,7 +3478,8 @@ Geben Sie eine Option für --source an, um den Vorgang fortzusetzen. Administratoreinstellungen verwalten - Verwalten Sie die Administratoreinstellungen von WinGet. + Verwalten Sie die Admineinstellungen von WinGet. + {Locked="WinGet"} Setzt alle Administratoreinstellungen zurück. @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. Neuer Wert Column title for listing the new value. + + Priorität; höhere Zahlen zuerst + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Priorität + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + Die Priorität der Quelle. Höhere Werte werden zuerst sortiert. + + + Die Ergebnisse wurden nach der höchsten übereinstimmenden Quellenpriorität gefiltert. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + Der angegebene DSC-Prozessor-Hash stimmt nicht mit dem Hash der Zieldatei überein. + \ No newline at end of file diff --git a/Localization/Resources/es-ES/winget.resw b/Localization/Resources/es-ES/winget.resw index c23b6387c4..f1cf534081 100644 --- a/Localization/Resources/es-ES/winget.resw +++ b/Localization/Resources/es-ES/winget.resw @@ -214,15 +214,19 @@ Deshabilitado + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. Habilitado + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. Característica + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. Vínculo + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. Las siguientes características experimentales están en curso. @@ -231,9 +235,11 @@ Se pueden configurar mediante el archivo de configuración "winget settings". Propiedad + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. Estado + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. Archivo al que se va a aplicar un algoritmo hash @@ -271,8 +277,8 @@ Se pueden configurar mediante el archivo de configuración "winget settings".Microsoft no es responsable, ni tampoco concede ninguna licencia de paquetes de terceros. - Este paquete se proporciona a través de Microsoft Store. Es posible que winget necesite adquirir el paquete de Microsoft Store en nombre del usuario actual. - {Locked="winget"} + Este paquete se proporciona a través de Microsoft Store. Es posible que WinGet tenga que adquirir el paquete desde Microsoft Store en nombre del usuario actual. + {Locked="WinGet"} Instala el paquete seleccionado, ya sea buscando en un origen configurado o directamente desde un manifiesto. De forma predeterminada, la consulta debe coincidir sin distinción entre mayúsculas y minúsculas con el id, el name o el moniker del paquete. Se pueden usar otros campos pasando su opción adecuada. De forma predeterminada, el comando install comprobará el estado de instalación del paquete e intentará realizar una actualización si procede. Reemplácelo por --force para realizar una instalación directa. @@ -324,7 +330,7 @@ Se pueden configurar mediante el archivo de configuración "winget settings". Directorios de WinGet - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs Configuración regional para usar (formato BCP47) @@ -351,7 +357,7 @@ Se pueden configurar mediante el archivo de configuración "winget settings".Ubicación del registro (si es posible) - Copyright (c) Microsoft Corporation. Todos los derechos reservados. + © 2026 Microsoft. Todos los derechos reservados. Página principal @@ -468,12 +474,14 @@ Se pueden configurar mediante el archivo de configuración "winget settings". Coincidencia + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. Nombre Origen + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. se omitieron otras entradas por el límite de resultados @@ -497,6 +505,7 @@ Se pueden configurar mediante el archivo de configuración "winget settings". Canal + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. Muestra información sobre un paquete específico. De forma predeterminada, la consulta debe coincidir con el id, nombre o el moniker del paquete sin distinguir entre mayúsculas y minúsculas. Puede usar otros campos si pasa su opción adecuada. @@ -515,6 +524,18 @@ Se pueden configurar mediante el archivo de configuración "winget settings".Solo el alias de un solo carácter puede aparecer después de un solo -: '{0}' {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + Ordenar resultados por una propiedad (se puede repetir) + Description for the --sort argument used to sort output by field name. + + + Ordenar los resultados en orden ascendente + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Ordenar los resultados en orden descendente + Description for the --descending (--desc) flag that sets descending sort direction for output. + Ya existe un origen con el nombre proporcionado y hace referencia a una ubicación diferente: @@ -616,8 +637,8 @@ Se pueden configurar mediante el archivo de configuración "winget settings".{Locked="source reset"} - No se pudo abrir el origen predefinido; por favor informa a los mantenedores de winget. - {Locked="winget"} + No se pudo abrir el origen predefinido; informe de ello a los mantenedores de WinGet. + {Locked="WinGet"} Eliminando todos los orígenes... @@ -672,7 +693,8 @@ Se pueden configurar mediante el archivo de configuración "winget settings".Filtrar resultados por etiqueta - Gracias por usar winget + Gracias por usar WinGet + {Locked="WinGet"} Avisos de terceros @@ -771,6 +793,7 @@ Se pueden configurar mediante el archivo de configuración "winget settings". Encontrado + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. La ruta de acceso es un directorio: {0} @@ -814,8 +837,8 @@ Se pueden configurar mediante el archivo de configuración "winget settings".Desinstalación realizada con éxito - winget no encuentra el comando de desinstalación de este paquete. Póngase en contacto con el anunciante del paquete para obtener ayuda. - {Locked="winget"} + WinGet no encuentra el comando de desinstalación para este paquete. Póngase en contacto con el publicador del paquete para obtener soporte técnico. + {Locked="WinGet"} Desinstalación abandonada @@ -998,6 +1021,7 @@ La configuración está deshabilitada debido a la Directiva de grupo. Externo + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". Los paquetes encontrados en esta importación tienen las siguientes dependencias: @@ -1344,6 +1368,10 @@ Especifique uno de ellos con la opción --source para continuar. Se encontró una versión más reciente, pero la tecnología de instalación es diferente de la versión actual instalada. Desinstale el paquete e instale la versión más reciente. + + {0} paquetes tienen actualizaciones bloqueadas porque las versiones más recientes usan una tecnología de instalación diferente a la de la instalación actual. Desinstale cada paquete e instale la versión más reciente. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + La tecnología de instalación de la versión más reciente especificada es diferente de la versión actual instalada. Desinstale el paquete e instale la versión más reciente. @@ -1462,6 +1490,7 @@ Especifique uno de ellos con la opción --source para continuar. No se puede purgar el directorio de instalación porque WinGet no lo ha creado + {Locked="WinGet"} Vínculo relacionado @@ -1724,7 +1753,8 @@ Especifique uno de ellos con la opción --source para continuar. {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - No se puede actualizar el paquete con winget. Use el método proporcionado por el publicador para actualizar este paquete. + El paquete no se puede actualizar con WinGet. Use el método proporcionado por el editor para actualizar este paquete. + {Locked="WinGet"} Actualizar paquetes aunque tengan un PIN que no sea de bloqueo @@ -2021,6 +2051,12 @@ Especifique uno de ellos con la opción --source para continuar. Omite el procesamiento de las dependencias del paquete y las características de Windows + + Instala solo las dependencias del paquete + + + Instalando solo dependencias. El paquete en sí no se instalará. + Dependencias omitidas. @@ -2111,7 +2147,8 @@ Especifique uno de ellos con la opción --source para continuar. Habilitar configuración de Administrador de paquetes de Windows - Recuperar información sobre errores. Dado un número, la salida contendrá detalles sobre el error, incluido el nombre del símbolo si se trata de un error específico de WinGet. Dado una cadena, se busca este valor en los errores específicos de WinGet. + Recuperar información sobre errores. Dado un número, la salida contendrá detalles sobre el error, incluido el nombre del símbolo si se trata de un error WinGet específico. Dada una cadena, se busca este valor en los WinGet errores específicos. + {Locked="WinGet"} Obtener información sobre errores @@ -2119,6 +2156,18 @@ Especifique uno de ellos con la opción --source para continuar. Valor para buscar en la información de error + + Generar el archivo Markdown del código de error + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + El argumento --output no se puede combinar con un valor de entrada. + {Locked="--output"} + + + Se debe proporcionar un valor o --output de entrada. + {Locked="--output"} + El número especificado es demasiado grande para ser HRESULT. @@ -2210,7 +2259,8 @@ Especifique uno de ellos con la opción --source para continuar. La directiva bloquea la aplicación Microsoft Store - La característica está actualmente en desarrollo. Se puede habilitar con la configuración de winget. + La característica está actualmente en desarrollo. Se puede habilitar usando `winget settings`. + {Locked="winget settings"} Error al instalar la aplicación Microsoft Store @@ -2254,6 +2304,9 @@ Especifique uno de ellos con la opción --source para continuar. No se encontraron actualizaciones aplicables + + Hay una actualización disponible, pero usa una tecnología de instalación diferente a la instalación actual + actualización de WinGet: todas completadas con errores @@ -2780,8 +2833,8 @@ Especifique uno de ellos con la opción --source para continuar. Especifique la cuenta que se usará para la autenticación - No se pudo agregar el origen. Esta versión de winget no admite el método de autenticación del origen. Intente actualizar a la versión de winget más reciente. - {Locked="winget"} + No se pudo agregar el origen. Esta versión WinGet no admite el método de autenticación del origen. Intente actualizar a la última versión WinGet. + {Locked="WinGet"} El origen de {0} requiere autenticación. La solicitud de autenticación puede aparecer cuando sea necesario. La información autenticada se compartirá con el origen para la autorización de acceso. @@ -2861,12 +2914,19 @@ Especifique uno de ellos con la opción --source para continuar. Habilitar Administrador de paquetes de Windows opciones de la línea de comandos del proxy Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Habilitar la ruta del procesador de configuración del Administrador de paquetes de Windows + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + Establecer un proxy para usar para esta ejecución Deshabilitar el uso del proxy para esta ejecución + + Deshabilita la barra de progreso y el indicador giratorio + No se puede restablecer {0}. Esta configuración está controlada por la directiva. Para obtener más información, póngase en contacto con el administrador del sistema. {Locked="{0}"} The value will be replaced with the feature name @@ -2908,7 +2968,8 @@ Especifique uno de ellos con la opción --source para continuar. Excluye un origen de la detección (true o false) - Explícito + Especificado explícitamente + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. Nivel de confianza del origen (ninguno o de confianza) @@ -3184,17 +3245,19 @@ Especifique uno de ellos con la opción --source para continuar. Familia + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - Formato - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + Variantes + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - Filtrar resultados por nombre de familia + Filtrar resultados por nombre de familia tipográfica + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - Cara - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + Variante + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. Rutas de acceso @@ -3219,8 +3282,8 @@ Especifique uno de ellos con la opción --source para continuar. El instalador del paquete requiere autenticación. La solicitud de autenticación puede aparecer cuando sea necesario. La información autenticada se compartirá con la dirección URL de descarga del instalador. - No se pudo descargar el instalador. Esta versión de winget no admite el método de autenticación de descarga del instalador. Intente actualizar a la versión de winget más reciente. - {Locked="winget"} + No se pudo descargar el instalador. Esta versión WinGet no admite el método de autenticación de descarga del instalador. Intente actualizar a la última versión WinGet. + {Locked="WinGet"} Error al descargar el instalador. Error de autenticación. @@ -3228,12 +3291,41 @@ Especifique uno de ellos con la opción --source para continuar. Especificar la ruta al procesador de configuración + + Ruta de acceso del procesador personalizado: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hash: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Tipo: alias de ejecución de la aplicación + Indicates the processor path is an app execution alias (MSIX package link). + + + Ruta de acceso: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Firmado por: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Firmado por: (sin firmar) + Indicates the custom processor executable does not have an Authenticode signature. + + + Error en la comprobación de integridad para la ruta de acceso del procesador DSC personalizado. Es posible que se haya modificado la ruta de acceso. + Error when the server-side hash of the processor path does not match the client-computed hash. + Comandos de recursos de DSC v3 DSC stands for "Desired State Configuration". It should already have a locked translation. Los subcomandos aquí implementan recursos de Desired State Configuration (DSC) v3 para configurar WinGet y paquetes. + {Locked="WinGet"} Obtener el estado del recurso @@ -3273,6 +3365,7 @@ Especifique uno de ellos con la opción --source para continuar. Administrar paquetes mediante WinGet. + {Locked="WinGet"} Indica si una instancia debe existir o no. @@ -3309,6 +3402,7 @@ Especifique uno de ellos con la opción --source para continuar. Administrar la configuración del usuario de WinGet. + {Locked="WinGet"} Contenido del archivo JSON de configuración. @@ -3324,6 +3418,7 @@ Especifique uno de ellos con la opción --source para continuar. Administrar las fuentes de WinGet. + {Locked="WinGet"} El nombre que se utilizará para el origen. @@ -3384,6 +3479,7 @@ Especifique uno de ellos con la opción --source para continuar. Administrar la configuración del administrador de WinGet. + {Locked="WinGet"} Restablecer todas las configuraciones de administración @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. Nuevo valor Column title for listing the new value. + + Prioridad; números más altos primero + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Prioridad + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + Prioridad del origen. Los valores más altos se ordenan primero en el orden. + + + Los resultados se han filtrado según la prioridad de origen coincidente más alta. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + El hash del procesador DSC proporcionado no coincide con el hash del archivo de destino. + \ No newline at end of file diff --git a/Localization/Resources/fr-FR/winget.resw b/Localization/Resources/fr-FR/winget.resw index 9e2aede069..db87d76595 100644 --- a/Localization/Resources/fr-FR/winget.resw +++ b/Localization/Resources/fr-FR/winget.resw @@ -214,15 +214,19 @@ Désactivé + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. Activé + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. Fonctionnalité + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. Lien + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. Les fonctionnalités expérimentales suivantes sont en cours. @@ -231,9 +235,11 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Propriété + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. Statut + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. Fichier à hacher @@ -271,8 +277,8 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Microsoft n’est pas responsable des paquets tiers et n’accorde pas de licences à ceux-ci. - Ce package est fourni via Microsoft Store. Le winget devra peut-être acquérir le package à partir du Microsoft Store pour le compte de l’utilisateur actuel. - {Locked="winget"} + Ce package est fourni via Microsoft Store. WinGet peut devoir récupérer le package depuis Microsoft Store pour le compte de l’utilisateur actuel. + {Locked="WinGet"} Installe le package sélectionné, trouvé en recherchant une source configurée ou directement à partir d’un manifeste. Par défaut, la requête doit correspondre à l’id, name ou moniker du package sans respecter la casse. D’autres champs peuvent être utilisés en passant leur option appropriée. Par défaut, la commande install vérifie l’état d’installation du package et tente d’effectuer une mise à niveau, le cas échéant. Remplacez par --force pour effectuer une installation directe. @@ -323,8 +329,8 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - Répertoires Winget - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + Répertoires de WinGet + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs Paramètres régionaux à utiliser (format BCP47) @@ -351,7 +357,7 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Emplacement du journal (si pris en charge) - Copyright (c) Microsoft Corporation. Tous droits réservés. + © 2026 Microsoft. Tous droits réservés. Page d’accueil @@ -468,12 +474,14 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Correspondance + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. Nom Source + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. entrées supplémentaires tronquées en raison de la limite de résultats @@ -497,6 +505,7 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Canal + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. Affiche des informations sur un package spécifique. Par défaut, la requête doit correspondre de façon non sensible à la casse à l’ID, au nom ou au moniker du package. D’autres champs peuvent être utilisés en passant l’option appropriée. @@ -515,6 +524,18 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Seul l’alias de caractère peut se produire après une seule – : '{0}' {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + Trier les résultats par une propriété (peut être répété) + Description for the --sort argument used to sort output by field name. + + + Trier les résultats par ordre croissant + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Trier les résultats par ordre décroissant + Description for the --descending (--desc) flag that sets descending sort direction for output. + Une source avec le nom spécifié existe déjà et fait référence à un autre emplacement : @@ -616,8 +637,8 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing {Locked="source reset"} - Échec de l’ouverture de la source prédéfinie ; veuillez le signaler aux chargés de maintenance winget. - {Locked="winget"} + Échec de l’ouverture de la source prédéfinie, veuillez le signaler aux chargés de maintenance WinGet. + {Locked="WinGet"} Suppression en cours de toutes les sources... Merci de patienter. @@ -672,7 +693,8 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Filtrer les résultats par mot-clé - Merci d’utiliser winget + Merci d’utiliser WinGet + {Locked="WinGet"} Avis de tiers @@ -771,6 +793,7 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Trouvé + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. Le chemin d’accès est un répertoire : {0} @@ -814,8 +837,8 @@ Elles peuvent être configurées par le biais du fichier de paramètres « wing Désinstallé avec succès - winget ne peut pas trouver la commande de désinstallation pour ce package. Veuillez contacter l’éditeur du package pour obtenir de l’aide. - {Locked="winget"} + WinGet ne trouve pas la commande de désinstallation de ce package. Contactez l’éditeur du package pour obtenir de l’aide. + {Locked="WinGet"} Désinstallation abandonnée @@ -998,6 +1021,7 @@ La configuration est désactivée en raison d’une stratégie de groupe. Externe + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". Les packages trouvés dans cette importation ont les dépendances suivantes : @@ -1344,6 +1368,10 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Une version plus récente a été trouvée, mais la technologie d’installation est différente de la version actuelle installée. Désinstallez le package et installez la version la plus récente. + + {0} mises à niveau du ou des packages sont bloquées, car les versions plus récentes utilisent une technologie d’installation différente de celle de l’installation actuelle. Désinstallez chaque package, puis installez la nouvelle version. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + La technologie d’installation de la version la plus récente spécifiée est différente de la version actuelle installée. Désinstallez le package et installez la version la plus récente. @@ -1462,6 +1490,7 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Impossible de vider le répertoire d’installation, car il n’a pas été créé par WinGet + {Locked="WinGet"} Lien associé @@ -1724,7 +1753,8 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - Impossible de mettre à niveau le package à l’aide de Winget. Utilisez la méthode fournie par l’éditeur pour mettre à niveau ce package. + Impossible de mettre à niveau le package à l’aide de WinGet. Utilisez la méthode fournie par l’éditeur pour mettre à niveau ce package. + {Locked="WinGet"} Mettre à niveau les packages même s’ils ont une épingle non bloquante @@ -2021,6 +2051,12 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Ignore le traitement des dépendances de package et des fonctionnalités Windows + + Installe uniquement les dépendances du package + + + Installation des dépendances uniquement. Le package lui-même ne sera pas installé. + Dépendances ignorées. @@ -2111,7 +2147,8 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Activer la configuration Gestionnaire de package Windows - Récupérez des informations sur les erreurs. En fonction d’un nombre, la sortie contiendra des détails sur l’erreur, y compris le nom du symbole s’il s’agit d’une erreur spécifique à winget. En fonction d’une chaîne, les erreurs spécifiques à winget sont recherchées pour cette valeur. + Récupérez des informations sur les erreurs. Selon un nombre, la sortie contiendra des détails sur l’erreur, y compris le nom du symbole s’il s’agit d’une erreur WinGet spécifique. En fonction d’une chaîne, les erreurs spécifiques aux WinGet sont recherchées pour cette valeur. + {Locked="WinGet"} Obtenir des informations sur les erreurs @@ -2119,6 +2156,18 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Valeur à rechercher dans les informations sur l’erreur + + Générer un fichier Markdown contenant un code d'erreur + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + L’argument --output ne peut pas être combiné avec une valeur d’entrée. + {Locked="--output"} + + + Une valeur d’entrée ou un --output doit être fourni. + {Locked="--output"} + Le nombre donné est trop grand pour être un HRESULT. @@ -2210,7 +2259,8 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue L’application Microsoft Store est bloquée par la stratégie - La fonctionnalité est en cours de développement. Il peut être activé à l’aide des paramètres winget. + La fonctionnalité est en cours de développement. Il peut être activé à l’aide de 'winget settings'. + {Locked="winget settings"} Échec de l’installation de l’application Microsoft Store @@ -2254,6 +2304,9 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Aucune mise à jour applicable trouvée + + Une mise à niveau est disponible, mais elle utilise une technologie d’installation différente de celle de l’installation actuelle + mises à niveau WinGet – toutes terminées sur des échecs @@ -2780,8 +2833,8 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Spécifier le compte à utiliser pour l’authentification - Échec de l’ajout de la source. Cette version winget ne prend pas en charge la méthode d’authentification de la source. Essayez de mettre à niveau vers la dernière version winget. - {Locked="winget"} + Échec de l’ajout de la source. Cette version WinGet ne prend pas en charge la méthode d’authentification de la source. Essayez de mettre à niveau vers la dernière version WinGet. + {Locked="WinGet"} La source {0} requiert une authentification. L’invite d’authentification peut s’afficher en cas de besoin. Les informations authentifiées seront partagées avec la source pour l’autorisation d’accès. @@ -2861,12 +2914,19 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Activer les options de ligne de commande du gestionnaire de paquets Windows Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Activer le chemin d'accès du processeur de configuration du Gestionnaire de package Windows + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + Définir un proxy à utiliser pour cette exécution Désactiver l’utilisation du proxy pour cette exécution + + Désactive la barre de progression et la boucle de progression + Impossible de réinitialiser {0}. Ce paramètre est contrôlé par la stratégie. Pour plus d’informations, contactez votre administrateur système. {Locked="{0}"} The value will be replaced with the feature name @@ -2908,7 +2968,8 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Exclut une source de la découverte (true ou false) - Contenu explicite + Explicite + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. Niveau de confiance de la source (aucun ou approuvé) @@ -3184,17 +3245,19 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Famille + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - Visages - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + Styles + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - Filtrer les résultats par nom de famille + Filtrer les résultats par nom de famille de polices + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - Visage - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + Style + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. Chemins d'accès @@ -3219,8 +3282,8 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Le programme d’installation du package nécessite une authentification. L’invite d’authentification peut s’afficher en cas de besoin. Les informations authentifiées seront partagées avec l’URL de téléchargement du programme d’installation. - Échec du téléchargement du programme d’installation. Cette version winget ne prend pas en charge la méthode d’authentification de téléchargement du programme d’installation. Essayez de mettre à niveau vers la dernière version winget. - {Locked="winget"} + Échec du téléchargement du programme d’installation. Cette version WinGet ne prend pas en charge la méthode d’authentification de téléchargement du programme d’installation. Essayez de mettre à niveau vers la dernière version WinGet. + {Locked="WinGet"} Le téléchargement du programme d'installation a échoué. L'authentification a échoué. @@ -3228,12 +3291,41 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Spécifiez le chemin d’accès au processeur de configuration + + Chemin d'accès du processeur personnalisé : + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hachage : {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Type : alias d’exécution d’application + Indicates the processor path is an app execution alias (MSIX package link). + + + Chemin d’accès : {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Signé par : {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Signé par : (non signé) + Indicates the custom processor executable does not have an Authenticode signature. + + + La vérification d'intégrité a échoué pour le chemin d’accès du processeur DSC personnalisé. Le chemin d’accès a peut-être été modifié. + Error when the server-side hash of the processor path does not match the client-computed hash. + Commandes de ressources DSC v3 DSC stands for "Desired State Configuration". It should already have a locked translation. - Les sous-commandes implémentent ici des ressources Desired State Configuration (DSC) v3 pour configurer winget et packages. + Les sous-commandes ici implémentent des ressources Desired State Configuration (DSC) v3 pour la configuration des WinGet et des packages. + {Locked="WinGet"} Obtenir l’état de la ressource @@ -3273,6 +3365,7 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Gérez les packages via WinGet. + {Locked="WinGet"} Indique si un instance doit ou existe. @@ -3308,7 +3401,8 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Gérer le fichier de paramètres utilisateur - Gérez les paramètres utilisateur de WinGet. + Gérez les paramètres d’utilisateur de WinGet. + {Locked="WinGet"} Contenu du fichier JSON des paramètres. @@ -3324,6 +3418,7 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Gérez les sources de WinGet. + {Locked="WinGet"} Nom à utiliser pour la source. @@ -3384,6 +3479,7 @@ Spécifiez l’un d’entre eux à l’aide de l’option --source pour continue Gérez les paramètres d’administrateur de WinGet. + {Locked="WinGet"} Réinitialise tous les paramètres d’administration @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. Nouvelle valeur Column title for listing the new value. + + Priorité : les nombres les plus élevés d’abord + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Priorité + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + Priorité de la source. Les valeurs les plus élevées sont triées en premier dans l’ordre. + + + Les résultats ont été filtrés selon la priorité source la plus élevée. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + Le hachage du processeur DSC fourni ne correspond pas au hachage du fichier cible. + \ No newline at end of file diff --git a/Localization/Resources/it-IT/winget.resw b/Localization/Resources/it-IT/winget.resw index 82cb960cac..8f0884d92a 100644 --- a/Localization/Resources/it-IT/winget.resw +++ b/Localization/Resources/it-IT/winget.resw @@ -214,15 +214,19 @@ Disabilitate + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. Abilitata + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. Funzionalità + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. Collegamento + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. Le seguenti funzionalità sperimentali sono in corso. @@ -231,9 +235,11 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Proprietà + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. Stato + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. File su cui eseguire l'hashing @@ -271,8 +277,8 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Microsoft non è responsabile né concede alcuna licenza a pacchetti di terze parti. - Questo pacchetto è stato fornito tramite Microsoft Store. winget potrebbe dover acquisire il pacchetto da Microsoft Store per conto dell'utente corrente. - {Locked="winget"} + Il pacchetto è stato fornito tramite Microsoft Store. WinGet potrebbe dover acquisire il pacchetto da Microsoft Store per conto dell'utente attuale. + {Locked="WinGet"} Installa il pacchetto selezionato, trovato eseguendo una ricerca in un'origine configurata o direttamente da un manifesto. Per impostazione predefinita, la query deve corrispondere senza distinzione tra maiuscole e minuscole all'id, name o moniker del pacchetto. È possibile usare altri campi passando l'opzione appropriata. Per impostazione predefinita, il comando install controllerà lo stato di installazione del pacchetto e tenterà di eseguire un aggiornamento, se applicabile. Eseguire l'override con --force per eseguire un'installazione diretta. @@ -324,7 +330,7 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Directory WinGet - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs Impostazioni locali da usare (formato BCP47) @@ -351,7 +357,7 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Percorso log (se supportato) - Copyright (c) Microsoft Corporation. Tutti i diritti sono riservati. + © 2026 Microsoft. Tutti i diritti riservati. Home page @@ -468,12 +474,14 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Corrispondenza + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. Nome Origine + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. voci aggiuntive troncate a causa del limite dei risultati @@ -497,6 +505,7 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Canale + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. Visualizza informazioni su un pacchetto specifico. Per impostazione predefinita, la query deve essere insensitively con l'ID, il nome o il moniker del pacchetto. Altri campi possono essere utilizzati passando l'opzione appropriata. @@ -515,6 +524,18 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Solo l'alias a carattere singolo può apparire dopo un singolo -: '{0}' {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + Ordina i risultati in base a una proprietà (ripetibile) + Description for the --sort argument used to sort output by field name. + + + Elenca i risultati in ordine crescente + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Elenca i risultati in ordine decrescente + Description for the --descending (--desc) flag that sets descending sort direction for output. + Esiste già un'origine con il nome specificato e si riferisce a un percorso diverso: @@ -616,8 +637,8 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< {Locked="source reset"} - Non è stato possibile aprire l'origine predefinita. Informare i gestori winget - {Locked="winget"} + Apertura dell'origine predefinita non riuscita. Informare i gestori di WinGet. + {Locked="WinGet"} Rimozione di tutte le origini in corso... @@ -673,6 +694,7 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Grazie per aver usato WinGet + {Locked="WinGet"} Comunicazioni di terze parti @@ -771,6 +793,7 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Trovato + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. Il percorso è una directory: {0} @@ -814,8 +837,8 @@ Possono essere configurati tramite il file di impostazioni ' winget settings '.< Disinstallazione completata - winget non riesce a individuare il comando di disinstallazione per questo pacchetto. Contattare l'autore del pacchetto per chiedere supporto. - {Locked="winget"} + WinGet impossibile individuare il comando di disinstallazione per questo pacchetto. Contattare l'editore del pacchetto per assistenza. + {Locked="WinGet"} Disinstallazione interrotta @@ -998,6 +1021,7 @@ La configurazione è disabilitata a causa dei criteri di gruppo. Esterno + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". I pacchetti trovati in questa importazione hanno le dipendenze seguenti: @@ -1344,6 +1368,10 @@ Specificarne uno utilizzando l'opzione --source per continuare. È stata trovata una versione più recente, ma la tecnologia di installazione è diversa dalla versione corrente installata. Disinstallare il pacchetto e installare la versione più recente. + + {0} pacchetti hanno aggiornamenti bloccati perché le versioni più recenti utilizzano una tecnologia di installazione diversa rispetto all'installazione corrente. Disinstalla ogni pacchetto, quindi installa la versione più recente. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + La tecnologia di installazione della versione più recente specificata è diversa dalla versione corrente installata. Disinstallare il pacchetto e installare la versione più recente. @@ -1462,6 +1490,7 @@ Specificarne uno utilizzando l'opzione --source per continuare. Non è possibile eliminare la directory di installazione, poiché non è stata creata da WinGet. + {Locked="WinGet"} Collegamento correlato @@ -1724,7 +1753,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - Non è possibile aggiornare il pacchetto con WinGet. Usare il metodo fornito dal server di pubblicazione per l'aggiornamento di questo pacchetto. + Non è possibile aggiornare il pacchetto con WinGet. Utilizzare il metodo fornito dall'autore per aggiornare il pacchetto. + {Locked="WinGet"} Aggiorna i pacchetti anche se hanno un PIN non bloccante @@ -2021,6 +2051,12 @@ Specificarne uno utilizzando l'opzione --source per continuare. Ignora l'elaborazione delle dipendenze dei pacchetti e delle funzionalità di Windows + + Installa solo le dipendenze del pacchetto + + + Installazione solo delle dipendenze. Il pacchetto stesso non verrà installato. + Dipendenze ignorate. @@ -2111,7 +2147,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. Abilita la configurazione di Gestione pacchetti - Recupera informazioni sugli errori. Dato un numero, l'output conterrà i dettagli sull'errore, incluso il nome del simbolo se si tratta di un errore specifico di WinGet. In base a una stringa, viene eseguita la ricerca di questo valore negli errori specifici di WinGet. + Recupera informazioni sugli errori. Dato un numero, l'output conterrà i dettagli sull'errore, incluso il nome del simbolo se si tratta di un errore specifico WinGet. Data una stringa, viene eseguita la ricerca degli errori specifici del WinGet per questo valore. + {Locked="WinGet"} Ottenere informazioni sugli errori @@ -2119,6 +2156,18 @@ Specificarne uno utilizzando l'opzione --source per continuare. Valore da cercare nelle informazioni sull'errore + + Genera un file Markdown per il codice di errore + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + L'argomento --output non può essere combinato con un valore di input. + {Locked="--output"} + + + È necessario specificare un valore o un --output di input. + {Locked="--output"} + Il numero specificato è troppo grande per essere un HRESULT. @@ -2210,7 +2259,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. L'applicazione Microsoft Store è bloccata dai criteri - La funzionalità è attualmente in fase di sviluppo. Può essere abilitato con le impostazioni di WinGet. + La funzionalità è attualmente in fase di sviluppo. Può essere abilitato usando 'winget settings'. + {Locked="winget settings"} Non è stato possibile installare l'app Microsoft Store @@ -2254,6 +2304,9 @@ Specificarne uno utilizzando l'opzione --source per continuare. Non è stato trovato alcun aggiornamento applicabile + + È disponibile un aggiornamento, ma usa una tecnologia di installazione diversa da quella dell'installazione attuale + aggiornamento di winget -- tutto completato con errori @@ -2780,8 +2833,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. Specificare l'account da usare per l'autenticazione - Non è stato possibile aggiungere l'origine. Questa versione winget non supporta il metodo di autenticazione dell'origine. Provare a eseguire l'aggiornamento alla versione più recente winget. - {Locked="winget"} + Non è stato possibile aggiungere l'origine. Questa versione WinGet non supporta il metodo di autenticazione dell'origine. Provare a eseguire l'aggiornamento alla versione più recente di WinGet. + {Locked="WinGet"} L'origine {0} richiede l'autenticazione. La richiesta di autenticazione può essere visualizzata quando necessario. Le informazioni autenticate verranno condivise con l'origine per l'autorizzazione di accesso. @@ -2861,12 +2914,19 @@ Specificarne uno utilizzando l'opzione --source per continuare. Abilita le opzioni della riga di comando del proxy Gestione pacchetti Windows Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Abilita l'override del processore di configurazione di Gestione pacchetti Windows + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + Imposta un proxy da usare per questa esecuzione Disabilita l'uso del proxy per questa esecuzione + + Disabilita la barra di avanzamento e la rotellina + Impossibile reimpostare {0}. Questa impostazione è controllata dai criteri. Per ulteriori informazioni, contattare l'amministratore di sistema. {Locked="{0}"} The value will be replaced with the feature name @@ -2908,13 +2968,14 @@ Specificarne uno utilizzando l'opzione --source per continuare. Esclude un'origine dall'individuazione (vero o falso) - Contenuti espliciti + Specificato + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. Livello di attendibilità dell'origine (nessuno o attendibile) - Livello di trust + Livello di attendibilità Non è possibile ottenere il catalogo pacchetti Microsoft Store. @@ -3184,17 +3245,19 @@ Specificarne uno utilizzando l'opzione --source per continuare. Famiglia + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - Facce - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + Tipi di carattere tipografici + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - Filtra i risultati per cognome + Filtra i risultati per nome della famiglia di caratteri + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - Viso - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + Tipo di carattere tipografico + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. Percorsi @@ -3219,8 +3282,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. Il programma di installazione del pacchetto richiede l'autenticazione. La richiesta di autenticazione può essere visualizzata quando necessario. Le informazioni autenticate verranno condivise con l'URL di download del programma di installazione. - Non è stato possibile scaricare il programma di installazione. Questa versione winget non supporta il metodo di autenticazione per il download del programma di installazione. Provare a eseguire l'aggiornamento alla versione più recente winget. - {Locked="winget"} + Non è stato possibile scaricare il programma di installazione. Questa versione WinGet non supporta il metodo di autenticazione per il download del programma di installazione. Provare a eseguire l'aggiornamento alla versione più recente di WinGet. + {Locked="WinGet"} Download del programma di installazione non riuscito. Autenticazione non riuscita. @@ -3228,12 +3291,41 @@ Specificarne uno utilizzando l'opzione --source per continuare. Specifica il percorso del processore di configurazione + + Percorso del processore personalizzato: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hash: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Tipo: Alias di esecuzione app + Indicates the processor path is an app execution alias (MSIX package link). + + + Percorso: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Firmato da: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Firmato da: (non firmato) + Indicates the custom processor executable does not have an Authenticode signature. + + + Controllo di integrità non riuscito per il percorso del processore DSC personalizzato. Il percorso potrebbe essere stato modificato. + Error when the server-side hash of the processor path does not match the client-computed hash. + Comandi delle risorse DSC v3 DSC stands for "Desired State Configuration". It should already have a locked translation. - I comandi secondari implementano le risorse Desired State Configuration (DSC) v3 per la configurazione di winget e pacchetti. + I sottocomandi qui implementano risorse Desired State Configuration (DSC) v3 per la configurazione di WinGet e dei pacchetti. + {Locked="WinGet"} Recupera stato della risorsa @@ -3272,7 +3364,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. Gestisci stato del pacchetto - Gestisci pacchetti tramite WinGet. + Gestisce i pacchetti tramite WinGet. + {Locked="WinGet"} Indica se un'istanza deve esistere o meno. @@ -3308,7 +3401,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. Gestisci file delle impostazioni utente - Gestisci le impostazioni utente di WinGet. + Gestisce le impostazioni utente di WinGet. + {Locked="WinGet"} Contenuto del file JSON delle impostazioni. @@ -3323,7 +3417,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. Gestisci configurazione di origine - Gestisci le origini di WinGet. + Gestisce le origini di WinGet. + {Locked="WinGet"} Nome da utilizzare per l'origine. @@ -3383,7 +3478,8 @@ Specificarne uno utilizzando l'opzione --source per continuare. Gestisci impostazioni amministratore - Gestisci impostazioni amministratore di WinGet. + Gestisce le impostazioni amministratore di WinGet. + {Locked="WinGet"} Reimposta tutte le impostazioni di amministrazione @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. Nuovo valore Column title for listing the new value. + + Priorità; prima i numeri più alti + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Priorità + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + Priorità dell'origine. I valori più alti vengono ordinati per primi nell'ordine. + + + I risultati sono stati filtrati in base alla priorità di origine corrispondente più alta. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + L'hash del processore DSC specificato non corrisponde all'hash del file di destinazione. + \ No newline at end of file diff --git a/Localization/Resources/ja-JP/winget.resw b/Localization/Resources/ja-JP/winget.resw index 3e56d55f3b..025c7a3042 100644 --- a/Localization/Resources/ja-JP/winget.resw +++ b/Localization/Resources/ja-JP/winget.resw @@ -214,15 +214,19 @@ 無効 + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. 有効 + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. 機能 + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. リンク + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. 次の試験的な機能が進行中です。 @@ -231,9 +235,11 @@ プロパティ + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. 状態 + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. ハッシュするファイル @@ -271,8 +277,8 @@ Microsoft はサードパーティのパッケージに対して責任を負わず、ライセンスも付与しません。 - このパッケージは Microsoft Store から提供されています。winget は、現在のユーザーに代わって Microsoft Store からパッケージを取得する必要がある場合があります。 - {Locked="winget"} + このパッケージは Microsoft Store から提供されています。WinGet は、現在のユーザーに代わって Microsoft Store からパッケージを取得する必要がある場合があります。 + {Locked="WinGet"} 構成されたソースを探して見つけるかマニフェストから直接見つけて、選んだパッケージをインストールします。既定では、そのクエリはパッケージの id、name、または moniker と大文字小文字の区別なく一致する必要があります。その他のフィールドは、適切なオプションを渡すと使えます。既定では、install コマンドは、パッケージのインストール済み状態を確認し、アップグレードがあればそれを実行しようとします。直接インストールするには、--force を指定してオーバーライドします。 @@ -323,8 +329,8 @@ {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - Winget ディレクトリ - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + WinGet ディレクトリ + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs 使用するロケール (BCP47 形式) @@ -351,7 +357,7 @@ ログの場所 (サポートされている場合) - Copyright (c) Microsoft Corporation. All rights reserved. + © 2026 Microsoft. All rights reserved. ホーム ページ @@ -468,12 +474,14 @@ 一致 + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. 名前 ソース + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. 結果制限により、エントリがさらに切り捨てられました @@ -497,6 +505,7 @@ チャネル + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. 特定のパッケージの情報を表示します。既定では、クエリはパッケージの ID、名前、モニカーに大文字小文字の区別なく一致する必要があります。その他のフィールドは、適切なオプションを渡すことで使用することができます。 @@ -515,6 +524,18 @@ 単一の - の後に出現できるのは、1 文字の別名のみです: '{0}' {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + プロパティで結果を並べ替える (繰り返し可能) + Description for the --sort argument used to sort output by field name. + + + 結果を昇順で並べ替える + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + 結果を降順で並べ替える + Description for the --descending (--desc) flag that sets descending sort direction for output. + 指定された名前のソースは既に存在し、別の場所を参照しています: @@ -616,8 +637,8 @@ {Locked="source reset"} - 定義済みのソースを開けませんでした; winget メンテナに報告してください。 - {Locked="winget"} + 定義済みのソースを開けませんでした。WinGet メンテナーに報告してください。 + {Locked="WinGet"} すべてのソースを削除しています... @@ -672,7 +693,8 @@ タグで結果をフィルター処理 - Winget をお使いいただきありがとうございます + WinGet をお使いいただきありがとうございます + {Locked="WinGet"} サード パーティに関する通知 @@ -771,6 +793,7 @@ 見つかりました + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. パスはディレクトリです: {0} @@ -814,8 +837,8 @@ 正常にアンインストールされました - winget は、このパッケージのアンインストールコマンドを見つけることができません。サポートについては、パッケージの発行元に問い合わせてください。 - {Locked="winget"} + WinGet でこのパッケージのアンインストール コマンドが見つかりません。サポートについては、パッケージの発行元にお問い合わせください。 + {Locked="WinGet"} アンインストールが中止されました @@ -998,6 +1021,7 @@ 外部 + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". このインポートで見つかったパッケージには、次の依存関係があります: @@ -1344,6 +1368,10 @@ 新しいバージョンが見つかりましたが、インストールテクノロジが現在インストールされているバージョンと異なります。パッケージをアンインストールして、新しいバージョンをインストールしてください。 + + 新しいバージョンでは現在のインストールとは異なるインストール テクノロジが使用されているため、{0} パッケージのアップグレードがブロックされています。各パッケージをアンインストールしてから、新しいバージョンをインストールします。 + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + 指定された新しいバージョンのインストール テクノロジが、現在インストールされているバージョンと異なります。パッケージをアンインストールして、新しいバージョンをインストールしてください。 @@ -1462,6 +1490,7 @@ WinGet によって作成されていないため、インストール ディレクトリを破棄することができません + {Locked="WinGet"} 関連リンク @@ -1724,7 +1753,8 @@ {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - winget を使用してパッケージをアップグレードすることはできません。このパッケージをアップグレードするには、発行元から提供された方法を使用してください。 + WinGet を使用してパッケージをアップグレードすることはできません。このパッケージをアップグレードするには、発行元から提供された方法を使用してください。 + {Locked="WinGet"} ブロックでないピンを持っている場合でもパッケージをアップグレードする @@ -1744,7 +1774,7 @@ {Locked="winget pin","--include-pinned"} Error shown when we block an upgrade due to the package being pinned - {0} 個のパッケージには、アップグレードを止めるピンがあります。'winget pin' コマンドを使用してピンを表示および編集します。--include-pinned 引数を使用すると、より多くの結果が表示される場合があります。 + {0} 個のパッケージにアップグレードを妨げるピンがあります。ピンを表示および編集するには、'winget pin' コマンドを使用します。'--include-pinned' 引数を使用すると、より多くの結果が表示される場合があります。 {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages @@ -2021,6 +2051,12 @@ パッケージの依存関係と Windows 機能の処理をスキップする + + パッケージの依存関係のみをインストールします + + + 依存関係のみをインストールしています。パッケージ自体はインストールされません。 + 依存関係がスキップされました。 @@ -2111,7 +2147,8 @@ Windows パッケージ マネージャーの構成を有効にする - エラーに関する情報を取得します。指定された数値は、winget 固有のエラーの場合のシンボル名を含む、エラーに関する詳細を出力に含めます。文字列を指定すると、winget 固有のエラーがこの値で検索されます。 + エラーに関する情報を取得します。数値を指定すると、出力には、WinGet 固有のエラーの場合はシンボル名を含む、エラーに関する詳細が含まれます。文字列を指定すると、WinGet 固有のエラーがこの値で検索されます。 + {Locked="WinGet"} エラーに関する情報を取得する @@ -2119,6 +2156,18 @@ エラー情報内で検索する値 + + エラー コード Markdown ファイルを生成する + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + --output 引数を入力値と組み合わせることはできません。 + {Locked="--output"} + + + 入力値または --output を指定する必要があります。 + {Locked="--output"} + 指定された数値が大きすぎて HRESULT にできません。 @@ -2210,7 +2259,8 @@ Microsoft Store アプリがポリシーによってブロックされています - この機能は現在開発中です。winget 設定を使用して有効にできます。 + この機能は現在開発中です。'winget settings' を使用して有効にできます。 + {Locked="winget settings"} Microsoft Store アプリをインストールできませんでした @@ -2254,6 +2304,9 @@ 適用可能な更新は見つかりませんでした + + アップグレードは利用できますが、現在のインストールとは異なるインストール テクノロジを使用しています + WinGet アップグレード -- すべて完了しましたが、エラーが発生しました @@ -2780,8 +2833,8 @@ 認証に使用するアカウントを指定します - ソースを追加できませんでした。この winget バージョンは、ソースの認証方法をサポートしていません。最新の winget バージョンにアップグレードしてみてください。 - {Locked="winget"} + ソースを追加できませんでした。この WinGet バージョンは、ソースの認証方法をサポートしていません。最新の WinGet バージョンにアップグレードしてみてください。 + {Locked="WinGet"} {0} ソースには認証が必要です。必要に応じて認証プロンプトが表示されることがあります。認証された情報は、アクセス承認のためにソースと共有されます。 @@ -2861,12 +2914,19 @@ プロキシ コマンド ライン オプションWindows パッケージ マネージャー有効にする Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Windows パッケージ マネージャー構成プロセッサー パスを有効にします + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + この実行に使用するプロキシを設定します この実行に対するプロキシの使用を無効にする + + 進行状況バーとスピナーを無効にします + {0} をリセットできません。この設定はポリシーによって制御されます。詳細については、システム管理者に問い合わせてください。 {Locked="{0}"} The value will be replaced with the feature name @@ -2908,7 +2968,8 @@ ソースを検出から除外します (true または false) - 成人指定 + 明示的 + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. ソースの信頼レベル (なしまたは信頼済み) @@ -3184,17 +3245,19 @@ ファミリ + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - フェイス - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + スタイル + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. ファミリ名で結果をフィルター処理する + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + スタイル + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. パス @@ -3219,8 +3282,8 @@ パッケージ インストーラーには認証が必要です。必要に応じて認証プロンプトが表示されることがあります。認証された情報はインストーラーのダウンロード URL と共有されます。 - インストーラーをダウンロードできませんでした。この winget バージョンは、インストーラーのダウンロード認証方法をサポートしていません。最新の winget バージョンにアップグレードしてみてください。 - {Locked="winget"} + インストーラーをダウンロードできませんでした。この WinGet バージョンでは、インストーラーのダウンロード認証方法がサポートされていません。最新の WinGet バージョンにアップグレードしてみてください。 + {Locked="WinGet"} インストーラーをダウンロードできませんでした。認証に失敗しました。 @@ -3228,12 +3291,41 @@ 構成プロセッサへのパスを指定する + + カスタム プロセッサ パス: + Displayed as a warning header when a custom --processor-path argument is provided. + + + ハッシュ: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + 種類: アプリ実行エイリアス + Indicates the processor path is an app execution alias (MSIX package link). + + + パス: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + 署名者: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + 署名者: (署名なし) + Indicates the custom processor executable does not have an Authenticode signature. + + + カスタム DSC プロセッサ パスの整合性チェックに失敗しました。パスが変更された可能性があります。 + Error when the server-side hash of the processor path does not match the client-computed hash. + DSC v3 リソース コマンド DSC stands for "Desired State Configuration". It should already have a locked translation. - ここでのサブコマンドは、winget とパッケージを構成するための Desired State Configuration (DSC) v3 リソースを実装しています。 + ここでのサブコマンドは、WinGet とパッケージを構成するための Desired State Configuration (DSC) v3 リソースを実装しています。 + {Locked="WinGet"} リソースの状態を取得する @@ -3272,7 +3364,8 @@ パッケージの状態を管理する - winget を使用してパッケージを管理します。 + WinGet を使用してパッケージを管理します。 + {Locked="WinGet"} インスタンスが存在するかどうかを示します。 @@ -3308,7 +3401,8 @@ ユーザー設定ファイルを管理する - winget のユーザー設定を管理します。 + WinGet のユーザー設定を管理します。 + {Locked="WinGet"} 設定 JSON ファイルの内容。 @@ -3323,7 +3417,8 @@ ソースの構成を管理する - winget のソースを管理します。 + WinGet のソースを管理します。 + {Locked="WinGet"} ソースに使用する名前。 @@ -3383,7 +3478,8 @@ 管理者設定を管理する - winget の管理者設定を管理します。 + WinGet の管理者設定を管理します。 + {Locked="WinGet"} すべての管理者設定をリセットする @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. 新しい値 Column title for listing the new value. + + 優先 順位; 大きい数字順 + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + 優先順位 + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + ソースの優先度。値が大きいほど順番の先頭に並べ替えられます。 + + + 結果は、一致したソースの優先度が最も高いものにフィルター処理されました。 + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + 指定された DSC プロセッサ ハッシュがターゲット ファイルのハッシュと一致しません。 + \ No newline at end of file diff --git a/Localization/Resources/ko-KR/winget.resw b/Localization/Resources/ko-KR/winget.resw index 6818b455f1..308f6999bc 100644 --- a/Localization/Resources/ko-KR/winget.resw +++ b/Localization/Resources/ko-KR/winget.resw @@ -214,15 +214,19 @@ 사용 안 함 + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. 사용 + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. 기능 + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. 링크 + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. 다음 실험적 기능이 진행 중입니다. @@ -231,9 +235,11 @@ 속성 + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. 상태 + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. 해시할 파일 @@ -271,8 +277,8 @@ Microsoft는 타사 패키지에 대한 책임을 지지 않고 라이선스를 부여하지도 않습니다. - 이 패키지는 Microsoft Store를 통해 제공됩니다. winget은 현재 사용자를 대신하여 Microsoft Store에서 패키지를 가져와야 할 수 있습니다. - {Locked="winget"} + 이 패키지는 Microsoft Store를 통해 제공됩니다. WinGet은 현재 사용자를 대신하여 Microsoft Store 패키지를 획득해야 할 수 있습니다. + {Locked="WinGet"} 구성된 원본을 검색하여 찾거나 매니페스트에서 직접 선택한 패키지를 설치합니다. 기본적으로 쿼리는 대소문자를 구분하지 않고 패키지의 id, 이름 (name) 또는 모니커와 (moniker) 일치해야 합니다. 다른 필드는 적절한 옵션을 전달하여 사용할 수 있습니다. 기본적으로 install 명령은 패키지 설치 상태를 확인하고 해당되는 경우 업그레이드를 시도합니다. 직접 설치를 수행하려면 --force로 재정의하세요. @@ -323,8 +329,8 @@ {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - Winget 디렉터리 - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + WinGet 디렉터리 + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs 사용할 로캘(BCP47 형식) @@ -351,7 +357,7 @@ 로그 위치(지원되는 경우) - Copyright (c) Microsoft Corporation. All rights reserved. + © 2026 Microsoft. All rights reserved. 홈페이지 @@ -468,12 +474,14 @@ 일치 + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. 이름 원본 + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. 결과 한도로 잘렸습니다. @@ -497,6 +505,7 @@ 채널 + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. 특정 패키지에 대한 정보를 표시합니다. 기본적으로 쿼리는 패키지의 ID, 이름 또는 모양과 대/소문자를 구분하지 않는 다섯 번이 어려져야 합니다. 적절한 옵션을 전달하여 다른 필드를 사용할 수 있습니다. @@ -515,6 +524,18 @@ 단일 문자 별칭만이 단일- 후에 나타남: '{0}' {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + 속성별로 결과를 정렬합니다. 여러 번 지정할 수 있습니다. + Description for the --sort argument used to sort output by field name. + + + 오름차순으로 결과 정렬 + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + 결과를 내림차순으로 정렬합니다. + Description for the --descending (--desc) flag that sets descending sort direction for output. + 지정한 이름의 원본이 이미 있으며 다른 위치를 참조합니다. @@ -616,8 +637,8 @@ {Locked="source reset"} - 미리 정의된 출처를 열지 못했습니다. 유지 관리 winget 신고하세요. - {Locked="winget"} + 미리 정의된 소스를 열지 못했습니다. WinGet 유지 관리자에게 보고하세요. + {Locked="WinGet"} 모든 원본을 제거하는 중... @@ -672,7 +693,8 @@ 태그를 기준으로 결과 필터링 - 위젯을 이용해 주셔서 감사합니다 + WinGet을 사용해 주셔서 감사합니다. + {Locked="WinGet"} 타사 통지 사항 @@ -771,6 +793,7 @@ 찾음 + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. 경로가 디렉터리임: {0} @@ -814,8 +837,8 @@ 성공적으로 제거됨 - winget 이 패키지에 대한 제거 명령을 찾을 수 없습니다. 지원을 위해 패키지 게시자에게 문의하세요. - {Locked="winget"} + WinGet 이 패키지에 대한 제거 명령을 찾을 수 없습니다. 지원이 필요하면 패키지 게시자에게 문의하세요. + {Locked="WinGet"} 제거가 중단됨 @@ -998,6 +1021,7 @@ 외부 + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". 이 가져오기에서 찾은 패키지에는 다음과 같은 종속성이 있습니다. @@ -1344,6 +1368,10 @@ 최신 버전을 찾았지만 설치 기술이 현재 설치된 버전과 다릅니다. 패키지를 제거하고 최신 버전을 설치하세요. + + 최신 버전에서 현재 설치와 다른 설치 기술을 사용하기 때문에 {0} 패키지의 업그레이드가 차단되었습니다. 각 패키지를 제거한 다음 최신 버전을 설치합니다. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + 지정된 최신 버전의 설치 기술이 현재 설치된 버전과 다릅니다. 패키지를 제거하고 최신 버전을 설치하세요. @@ -1462,6 +1490,7 @@ WinGet에서 만들지 않았으므로 설치 디렉터리를 제거할 수 없습니다. + {Locked="WinGet"} 관련 링크 @@ -1724,7 +1753,8 @@ {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - winget을 사용하여 패키지를 업그레이드할 수 없습니다. 게시자가 제공한 메서드를 사용하여 이 패키지를 업그레이드하세요. + WinGet를 사용하여 패키지를 업그레이드할 수 없습니다. 게시자가 제공한 방법을 사용하여 이 패키지를 업그레이드하세요. + {Locked="WinGet"} 차단되지 않는 PIN이 있는 경우에도 패키지 업그레이드 @@ -2021,6 +2051,12 @@ 패키지 종속성 및 Windows 기능 처리를 건너뜁니다. + + 패키지의 종속성만 설치합니다. + + + 종속성만 설치하는 중입니다. 패키지 자체는 설치되지 않습니다. + 종속성을 건너뛰었습니다. @@ -2111,7 +2147,8 @@ Windows 패키지 관리자 구성 사용 - 오류에 대한 정보를 검색합니다. 숫자로 지정하면 출력에 winget 특정 오류인 경우 기호 이름을 포함하여 오류에 대한 세부 정보가 포함됩니다. 문자열이 지정되면 winget 관련 오류가 이 값을 검색합니다. + 오류에 대한 정보를 검색합니다. 숫자가 지정되면 출력에는 WinGet 특정 오류인 경우 기호 이름을 포함하여 오류에 대한 세부 정보가 포함됩니다. 문자열이 주어지면 이 값에 대해 WinGet별 오류가 검색됩니다. + {Locked="WinGet"} 오류에 대한 정보 가져오기 @@ -2119,6 +2156,18 @@ 오류 정보 내에서 검색할 값입니다. + + 오류 코드 Markdown 파일 생성 + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + --output 인수는 입력 값과 함께 사용할 수 없습니다. + {Locked="--output"} + + + 입력 값 또는 --output 제공해야 합니다. + {Locked="--output"} + 주어진 숫자가 너무 커서 HRESULT가 될 수 없습니다. @@ -2210,7 +2259,8 @@ Microsoft Store 앱이 정책에 의해 차단됨 - 이 기능은 현재 개발 중입니다. winget 설정을 사용하여 사용하도록 설정할 수 있습니다. + 이 기능은 현재 개발 중입니다. `winget settings`를 사용하여 사용하도록 설정할 수 있습니다. + {Locked="winget settings"} Microsoft Store 앱을 설치하지 못했습니다. @@ -2254,6 +2304,9 @@ 적용 가능한 업데이트를 찾을 수 없음 + + 업그레이드는 사용할 수 있지만 현재 설치와는 다른 설치 기술을 사용합니다. + winget 업그레이드 -- 모두 실패로 완료됨 @@ -2780,8 +2833,8 @@ 인증에 사용할 계정 지정 - 원본을 추가하지 못했습니다. 이 winget 버전에서는 원본의 인증 방법을 지원하지 않습니다. 최신 winget 버전으로 업그레이드해 보세요. - {Locked="winget"} + 원본을 추가하지 못했습니다. 이 WinGet 버전은 원본의 인증 방법을 지원하지 않습니다. 최신 WinGet 버전으로 업그레이드해 보세요. + {Locked="WinGet"} {0} 원본을 인증해야 합니다. 필요한 경우 인증 프롬프트가 표시될 수 있습니다. 인증된 정보는 액세스 권한 부여를 위해 원본과 공유됩니다. @@ -2861,12 +2914,19 @@ 프록시 명령줄 옵션 Windows 패키지 관리자 사용 Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Windows 패키지 관리자 구성 프로세서 경로 사용 + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + 이 실행에 사용할 프록시 설정 이 실행에 프록시 사용 안 함 + + 진행률 표시줄과 회전 표시를 사용하지 않습니다. + {0} 다시 설정할 수 없습니다. 이 설정은 정책에 의해 제어됩니다. 자세한 내용은 시스템 관리자에게 문의하십시오. {Locked="{0}"} The value will be replaced with the feature name @@ -2908,10 +2968,11 @@ 검색에서 원본 제외(true 또는 false) - 유해 콘텐츠 + 명시적 + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. - 원본의 신뢰 수준(없음 또는 신뢰할 수 없음) + 원본의 신뢰 수준(없음 또는 신뢰할 수 있음) 신뢰 수준 @@ -3184,17 +3245,19 @@ 가족 + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - 얼굴 - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + 글꼴 스타일 + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - 성으로 결과 필터링 + 글꼴 패밀리 이름으로 결과 필터링 + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - 얼굴 - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + 글꼴 스타일 + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. 경로 @@ -3219,8 +3282,8 @@ 패키지 설치 관리자에 인증이 필요합니다. 필요한 경우 인증 프롬프트가 표시될 수 있습니다. 인증된 정보는 설치 관리자 다운로드 URL과 공유됩니다. - 설치 관리자를 다운로드하지 못했습니다. 이 winget 버전에서는 설치 관리자 다운로드 인증 방법을 지원하지 않습니다. 최신 winget 버전으로 업그레이드해 보세요. - {Locked="winget"} + 설치 관리자를 다운로드하지 못했습니다. 이 WinGet 버전은 설치 관리자 다운로드 인증 방법을 지원하지 않습니다. 최신 WinGet 버전으로 업그레이드해 보세요. + {Locked="WinGet"} 설치 관리자를 다운로드하지 못했습니다. 인증에 실패했습니다. @@ -3228,12 +3291,41 @@ 구성 프로세서의 경로를 지정합니다. + + 사용자 지정 프로세서 경로: + Displayed as a warning header when a custom --processor-path argument is provided. + + + 해시: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + 형식: 앱 실행 별칭 + Indicates the processor path is an app execution alias (MSIX package link). + + + 경로: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + 서명자: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + 서명자: (서명되지 않음) + Indicates the custom processor executable does not have an Authenticode signature. + + + 사용자 지정 DSC 프로세서 경로에 대한 무결성 검사에 실패했습니다. 경로가 수정되었을 수 있습니다. + Error when the server-side hash of the processor path does not match the client-computed hash. + DSC v3 리소스 명령 DSC stands for "Desired State Configuration". It should already have a locked translation. 여기서 하위 명령은 WinGet 및 패키지를 구성하기 위한 DSC(Desired State Configuration) v3 리소스를 구현합니다. + {Locked="WinGet"} 리소스 상태 가져오기 @@ -3272,7 +3364,8 @@ 패키지 상태 관리 - WinGet을 통해 패키지를 관리하세요. + WinGet을 통해 패키지를 관리합니다. + {Locked="WinGet"} instance 있는지 여부를 나타냅니다. @@ -3309,6 +3402,7 @@ WinGet의 사용자 설정을 관리합니다. + {Locked="WinGet"} 설정 JSON 파일 콘텐츠입니다. @@ -3323,7 +3417,8 @@ 데이터 원본 구성 - WinGet의 소스를 관리합니다. + WinGet의 원본을 관리합니다. + {Locked="WinGet"} 원본에 사용할 이름입니다. @@ -3384,6 +3479,7 @@ WinGet의 관리자 설정을 관리합니다. + {Locked="WinGet"} 모든 관리자 설정을 초기화합니다. @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. 새 값 Column title for listing the new value. + + 우선순위. 숫자가 높을수록 먼저 적용됩니다. + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + 우선순위 + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + 원본의 우선 순위입니다. 값이 클수록 순서가 먼저 정렬됩니다. + + + 결과가 가장 일치하는 원본 우선 순위로 필터링되었습니다. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + DSC 프로세서에서 제공한 해시가 대상 파일의 해시와 일치하지 않습니다. + \ No newline at end of file diff --git a/Localization/Resources/pt-BR/winget.resw b/Localization/Resources/pt-BR/winget.resw index 543bb86b1a..8456e63458 100644 --- a/Localization/Resources/pt-BR/winget.resw +++ b/Localization/Resources/pt-BR/winget.resw @@ -214,15 +214,19 @@ Desabilitado + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. Habilitado + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. Recurso + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. Link + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. Os seguintes recursos experimentais estão em andamento. @@ -231,9 +235,11 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Propriedade + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. Status + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. Arquivo em que será gerado o hash @@ -271,8 +277,8 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett A Microsoft não é responsável por, nem concede licenças a pacotes de terceiros. - Este pacote é fornecido por meio da Microsoft Store. O winget talvez precise adquirir o pacote da Microsoft Store em nome do usuário atual. - {Locked="winget"} + Este pacote é fornecido por meio da Microsoft Store. O WinGet talvez precise adquirir o pacote da Microsoft Store em nome do usuário atual. + {Locked="WinGet"} Instala o pacote selecionado, seja encontrado pesquisando uma origem configurada ou diretamente de um manifesto. Por padrão, a consulta deve corresponder a maiúsculas e minúsculas a id, name ou moniker do pacote. Outros campos podem ser usados passando sua opção apropriada. Por padrão, o comando install verificará o status do pacote instalado e tentará realizar uma atualização, se aplicável. Substitua por --force para executar uma instalação direta. @@ -323,8 +329,8 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - Diretórios Winget - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + Diretórios do WinGet + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs Local para usar (formato BCP47) @@ -351,7 +357,7 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Local do log (se houver suporte) - Copyright (c) Microsoft Corporation. Todos os direitos reservados. + © 2026 Microsoft. Todos os direitos reservados. Home page @@ -468,12 +474,14 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Correspondência + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. Nome Origem + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. entradas adicionais truncadas devido ao limite de resultado @@ -497,6 +505,7 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Canal + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. Mostra informações sobre um pacote específico. Por padrão, a consulta deve corresponder de forma insensível à ID, ao nome ou ao moniker do pacote. Outros campos podem ser usados passando a opção apropriada. @@ -515,6 +524,18 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Somente o alias de caractere único pode ocorrer após um único -: '{0}' {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + Classificar resultados por uma propriedade (pode ser repetido) + Description for the --sort argument used to sort output by field name. + + + Classificar resultados de A a Z + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Classificar resultados de Z a A + Description for the --descending (--desc) flag that sets descending sort direction for output. + Uma origem com o nome fornecido já existe e se refere a um local diferente: @@ -616,8 +637,8 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett {Locked="source reset"} - Falha ao abrir a origem predefinida; Relate aos mantenedores winget. - {Locked="winget"} + Falha ao abrir a fonte predefinida; informe aos mantenedores WinGet. + {Locked="WinGet"} Removendo todas as fontes... @@ -672,7 +693,8 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Filtrar resultados por rótulo - Obrigado por usar o winget + Obrigado por usar o WinGet + {Locked="WinGet"} Avisos de Terceiros @@ -771,6 +793,7 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Encontrado + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. O caminho é um diretório: {0} @@ -814,8 +837,8 @@ Eles podem ser configurados por meio do arquivo de configurações ' winget sett Desinstalado com êxito - winget não é possível localizar o comando Uninstall deste pacote. Entre no fornecedor do pacote para obter suporte. - {Locked="winget"} + WinGet não pode localizar o comando de desinstalação deste pacote. Entre em contato com o fornecedor do pacote para obter suporte. + {Locked="WinGet"} Desinstalação abandonada @@ -998,6 +1021,7 @@ A configuração está desabilitada devido à política de grupo. Externo + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". Os pacotes encontrados nesta importação têm as seguintes dependências: @@ -1344,6 +1368,10 @@ Especifique um deles usando a opção --source para continuar. Uma versão mais recente foi encontrada, mas a tecnologia de instalação é diferente da versão atual instalada. Desinstale o pacote e instale a versão mais recente. + + {0} pacote(s) têm atualizações bloqueadas porque as versões mais recentes usam uma tecnologia de instalação diferente da instalação atual. Desinstale cada pacote e instale a versão mais recente. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + A tecnologia de instalação da versão mais recente especificada é diferente da versão atual instalada. Desinstale o pacote e instale a versão mais recente. @@ -1461,7 +1489,8 @@ Especifique um deles usando a opção --source para continuar. Limpando o diretório de instalação... - Não é possível limpar o diretório de instalação porque ele não foi criado pelo winget + Não é possível limpar o diretório de instalação, pois ele não foi criado pelo WinGet + {Locked="WinGet"} Link Relacionado @@ -1724,7 +1753,8 @@ Especifique um deles usando a opção --source para continuar. {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - O pacote não pode ser atualizado usando winget. Use o método fornecido pelo editor para atualizar este pacote. + O pacote não pode ser atualizado usando WinGet. Use o método fornecido pelo editor para atualizar este pacote. + {Locked="WinGet"} Atualizar pacotes mesmo que eles tenham um marcador sem bloqueio @@ -1744,7 +1774,7 @@ Especifique um deles usando a opção --source para continuar. {Locked="winget pin","--include-pinned"} Error shown when we block an upgrade due to the package being pinned - {0} pacotes têm pinos que impedem a atualização. Use o 'winget pin' para exibir e editar pinos. O uso do --include-pinned pode mostrar mais resultados. + {0} pacote(s) têm pinos que impedem a atualização. Use o comando 'winget pin' para exibir e editar marcações. Usar o argumento '--include-pinned' pode mostrar mais resultados. {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages @@ -2021,6 +2051,12 @@ Especifique um deles usando a opção --source para continuar. Ignora o processamento de dependências de pacote e recursos do Windows + + Instala apenas as dependências do pacote + + + Instalando somente dependências. O pacote em si não será instalado. + Dependências ignoradas. @@ -2111,7 +2147,8 @@ Especifique um deles usando a opção --source para continuar. Habilitar as Configurações do Gerenciador de Pacotes do Windows - Recuperar informações sobre erros. Dado um número, a saída conterá detalhes sobre o erro, incluindo o nome do símbolo se for um erro específico do winget. Dado uma cadeia de caracteres, os erros específicos do winget são pesquisados para esse valor. + Recupere informações sobre erros. Dado um número, a saída conterá detalhes sobre o erro, incluindo o nome do símbolo se for um erro WinGet específico. Dada uma cadeia de caracteres, os WinGet erros específicos são pesquisados para esse valor. + {Locked="WinGet"} Obter informações sobre erros @@ -2119,6 +2156,18 @@ Especifique um deles usando a opção --source para continuar. Um valor a ser pesquisado dentro das informações de erro + + Gerar arquivo markdown de código de erro + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + O argumento --output não pode ser combinado com um valor de entrada. + {Locked="--output"} + + + Um valor ou --output de entrada deve ser fornecido. + {Locked="--output"} + O número fornecido é muito grande para ser um HRESULT. @@ -2210,7 +2259,8 @@ Especifique um deles usando a opção --source para continuar. O aplicativo da Microsoft Store está bloqueado pela política - O recurso está atualmente em desenvolvimento. Pode ser habilitado usando configurações de winget. + O recurso está atualmente em desenvolvimento. Ele pode ser habilitado usando 'winget settings'. + {Locked="winget settings"} Falha ao instalar o aplicativo da Microsoft Store @@ -2254,6 +2304,9 @@ Especifique um deles usando a opção --source para continuar. Nenhuma atualização aplicável foi encontrada + + Uma atualização está disponível, mas usa uma tecnologia de instalação diferente da instalação atual + A atualização do winget --all foi concluída com falhas @@ -2780,8 +2833,8 @@ Especifique um deles usando a opção --source para continuar. Especifique a conta a ser usada para autenticação - Falha ao adicionar origem. Esta winget não dá suporte ao método de autenticação da origem. Tente atualizar para a versão mais winget versão. - {Locked="winget"} + Falha ao adicionar origem. Esta versão WinGet não dá suporte ao método de autenticação da origem. Tente atualizar para a versão mais recente do WinGet. + {Locked="WinGet"} A fonte {0} requer autenticação. O prompt de autenticação pode aparecer quando necessário. As informações autenticadas serão compartilhadas com a origem para autorização de acesso. @@ -2861,12 +2914,19 @@ Especifique um deles usando a opção --source para continuar. Habilitar Gerenciador de Pacotes do Windows de linha de comando do proxy atual Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Habilitar caminho do processador de configuração do Gerenciador de Pacotes do Windows + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + Definir um proxy a ser usado para esta execução Desabilitar o uso de proxy para esta execução + + Desabilita a barra de progresso e o controle giratório + Não é possível redefinir {0}. Esta configuração é controlada pela política. Para obter mais informações, contate o administrador do sistema. {Locked="{0}"} The value will be replaced with the feature name @@ -2908,7 +2968,8 @@ Especifique um deles usando a opção --source para continuar. Exclui uma origem da descoberta (true ou false) - Explícito + Especificado explicitamente + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. Nível de confiança da origem (nenhum ou confiável) @@ -3184,17 +3245,19 @@ Especifique um deles usando a opção --source para continuar. Família + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - Fonte - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + Variações + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. Filtrar resultados por nome da família + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - Rosto - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + Variação + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. Caminhos @@ -3219,8 +3282,8 @@ Especifique um deles usando a opção --source para continuar. O instalador do pacote requer autenticação. O prompt de autenticação pode aparecer quando necessário. As informações autenticadas serão compartilhadas com a URL de download do instalador. - Falha ao baixar o instalador. Esta winget não dá suporte ao método de autenticação de download do instalador. Tente atualizar para a versão mais winget versão. - {Locked="winget"} + Falha ao baixar o instalador. Esta versão WinGet não dá suporte ao método de autenticação de download do instalador. Tente atualizar para a versão mais recente do WinGet. + {Locked="WinGet"} Falha ao baixar o instalador. Falha na autenticação. @@ -3228,12 +3291,41 @@ Especifique um deles usando a opção --source para continuar. Especificar o caminho para o processador de configuração + + Caminho do processador personalizado: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hash: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Tipo: alias de execução do aplicativo + Indicates the processor path is an app execution alias (MSIX package link). + + + Caminho: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Assinado por: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Assinado por: (sem assinatura) + Indicates the custom processor executable does not have an Authenticode signature. + + + Falha na marcar de integridade para o caminho do processador DSC personalizado. O caminho pode ter sido modificado. + Error when the server-side hash of the processor path does not match the client-computed hash. + Comandos de recurso DSC v3 DSC stands for "Desired State Configuration". It should already have a locked translation. - Os sub-comandos aqui implementam recursos Desired State Configuration (DSC) v3 para configurar o winget e os pacotes. + Os subcomandos aqui implementam recursos Desired State Configuration (DSC) v3 para configurar WinGet e pacotes. + {Locked="WinGet"} Obter o estado do recurso @@ -3273,6 +3365,7 @@ Especifique um deles usando a opção --source para continuar. Gerencie pacotes por meio do WinGet. + {Locked="WinGet"} Indica se uma instância deve ou não existir. @@ -3309,6 +3402,7 @@ Especifique um deles usando a opção --source para continuar. Gerencie as configurações de usuário do WinGet. + {Locked="WinGet"} O conteúdo do arquivo JSON de configurações. @@ -3324,6 +3418,7 @@ Especifique um deles usando a opção --source para continuar. Gerencie as fontes do WinGet. + {Locked="WinGet"} O nome a ser usado para a origem. @@ -3384,6 +3479,7 @@ Especifique um deles usando a opção --source para continuar. Gerencie as configurações de administrador do WinGet. + {Locked="WinGet"} Redefinindo todas as configurações de administrador @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. Novo Valor Column title for listing the new value. + + Prioridade; números mais altos primeiro + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Prioridade + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + A prioridade da origem. Os valores mais altos são classificados primeiro na ordem. + + + Os resultados foram filtrados para a prioridade de origem correspondente mais alta. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + O hash do processador DSC fornecido não corresponde ao hash do arquivo de destino. + \ No newline at end of file diff --git a/Localization/Resources/ru-RU/winget.resw b/Localization/Resources/ru-RU/winget.resw index 990913d8e2..fdb1af2af8 100644 --- a/Localization/Resources/ru-RU/winget.resw +++ b/Localization/Resources/ru-RU/winget.resw @@ -214,15 +214,19 @@ Отключено + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. Включено + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. Компонент + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. Ссылка + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. Следующие экспериментальные компоненты находятся в процессе разработки. @@ -231,9 +235,11 @@ Свойство + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. Состояние + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. Файл для хэширования @@ -271,8 +277,8 @@ Корпорация Майкрософт не несет ответственность за сторонние пакеты и не предоставляет для них никакие лицензии. - Этот пакет предоставляется через Microsoft Store. Программе winget может потребоваться получить пакет в Microsoft Store от имени текущего пользователя. - {Locked="winget"} + Этот пакет предоставляется через Microsoft Store. Программе WinGet может потребоваться получить пакет от Microsoft Store от имени текущего пользователя. + {Locked="WinGet"} Устанавливает выбранный пакет, найденный путем поиска в настроенном источнике или непосредственно из манифеста. По умолчанию запрос должен сравнить id, name или moniker пакета (без учета регистра). Можно использовать и другие поля, указав соответствующий параметр. По умолчанию команда установки проверяет состояние установки пакета и, если применимо, пытается выполнить обновление. Переопределите с помощью параметра --force для прямой установки. @@ -324,7 +330,7 @@ Каталоги WinGet - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs Используемый языковой стандарт (формат BCP47) @@ -351,7 +357,7 @@ Расположение журнала (если поддерживается) - (с) Корпорация Майкрософт (Microsoft Corporation). Все права защищены. + © Корпорация Майкрософт (Microsoft), 2026. Все права защищены. Домашняя страница @@ -468,12 +474,14 @@ Совпадение + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. Имя Источник + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. дополнительные записи усечены из-за ограничения количества результатов @@ -497,6 +505,7 @@ Канал + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. Отображает сведения о конкретном пакете. По умолчанию запрос должен сравнить идентификатор, имя или моникер пакета (без учета регистра). Можно использовать и другие поля, указав соответствующий параметр. @@ -515,6 +524,18 @@ Только один псевдоним символа может появляться после одного символа -: "{0}" {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + Сортировать результаты по свойству (может повторяться) + Description for the --sort argument used to sort output by field name. + + + Сортировать результаты по возрастанию + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Сортировать результаты по убыванию + Description for the --descending (--desc) flag that sets descending sort direction for output. + Источник с указанным именем уже существует и ссылается на другое расположение: @@ -616,8 +637,8 @@ {Locked="source reset"} - Не удалось открыть предопределенный источник. Сообщите об этом специалистам по обслуживанию winget. - {Locked="winget"} + Не удалось открыть заданный источник. Сообщите об этом специалистам по обслуживанию WinGet. + {Locked="WinGet"} Удаление всех источников... @@ -672,7 +693,8 @@ Фильтровать результаты по тегу - Благодарим за использование виджета + Благодарим за использование WinGet + {Locked="WinGet"} Уведомления третьих сторон @@ -771,6 +793,7 @@ Найдено + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. Указан путь к каталогу: {0} @@ -814,8 +837,8 @@ Удалено - winget не может найти команду удаления для пакета. Обратитесь за помощью к издателю пакетов. - {Locked="winget"} + WinGet не может найти команду удаления для пакета. Обратитесь за помощью к издателю пакетов. + {Locked="WinGet"} Удаление прекращено @@ -998,6 +1021,7 @@ Внешние + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". У пакетов, обнаруженных в этом импорте, следующие зависимости: @@ -1344,6 +1368,10 @@ Обнаружена более новая версия, но технология установки отличается от текущей установленной версии. Удалите пакет и установите более новую версию. + + Обновления {0} пакетов заблокированы, так как в более новых версиях используется технология установки, отличная от текущей. Удалите каждый пакет, а затем установите новую версию. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + Указанная технология установки более новой версии отличается от текущей установленной версии. Удалите пакет и установите более новую версию. @@ -1462,6 +1490,7 @@ Не удается очистить каталог установки, так как он не был создан с помощью WinGet + {Locked="WinGet"} Связанная ссылка @@ -1724,7 +1753,8 @@ {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - Невозможно обновить пакет с помощью winget. Для обновления этого пакета используйте метод, предоставленный издателем. + Невозможно установить новую версию пакета с помощью WinGet. Для установки новой версии этого пакета используйте метод, предоставленный издателем. + {Locked="WinGet"} Обновление пакетов, даже если они используют неблокирующее закрепление @@ -2021,6 +2051,12 @@ Пропускает обработку зависимостей пакетов и компонентов Windows + + Устанавливает только зависимости пакета + + + Устанавливаются только зависимости. Сам пакет установлен не будет. + Зависимости пропущены. @@ -2111,7 +2147,8 @@ Включить конфигурацию Диспетчера пакетов Windows - Получение сведений об ошибках. Если указано число, выходные данные будут содержать сведения об ошибке, включая имя символа, если это ошибка winget. При указании строки для этого значения будут искаться ошибки, относящиеся к winget. + Получить информацию об ошибках. Если задано число, выданные результаты будут содержать сведения об ошибке (в том числе имя символа, если это ошибка, специфичная для WinGet). Если задана строка, выполняется поиск ошибок, специфичных для WinGet, по этому значению. + {Locked="WinGet"} Получить информацию об ошибках @@ -2119,6 +2156,18 @@ Значение для поиска в сведениях об ошибке + + Сгенерировать Markdown-файл с кодами ошибок + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + Аргумент --output нельзя комбинировать с входным значением. + {Locked="--output"} + + + Необходимо указать входное значение или --output. + {Locked="--output"} + Указанное число слишком велико для HRESULT. @@ -2210,7 +2259,8 @@ Приложение Microsoft Store заблокировано политикой - Эта функция сейчас находится в разработке. Его можно включить с помощью параметров winget. + Эта функция сейчас находится в разработке. Ее можно включить с помощью команды "winget settings". + {Locked="winget settings"} Не удалось установить приложение Microsoft Store @@ -2254,6 +2304,9 @@ Применимые обновления не найдены + + Доступно обновление, но оно использует технологию установки, отличную от текущей + Команда winget upgrade --all завершилась с ошибками @@ -2780,8 +2833,8 @@ Укажите учетную запись, которая будет использоваться для проверки подлинности - Не удалось добавить источник. Эта winget не поддерживает метод проверки подлинности источника. Попробуйте обновиться до последней winget версии. - {Locked="winget"} + Не удалось добавить источник. Эта версия WinGet не поддерживает метод проверки подлинности источника. Попробуйте обновить до последней версии WinGet. + {Locked="WinGet"} Источник {0} требует проверки подлинности. При необходимости может появиться запрос проверки подлинности. Сведения, прошедшие проверку подлинности, будут предоставлены источнику для авторизации доступа. @@ -2861,12 +2914,19 @@ Включить Диспетчер пакетов Windows командной строки прокси-сервера Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Включить путь обработчика конфигурации Диспетчера пакетов Windows + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + Задать прокси-сервер для этого выполнения Отключить использование прокси-сервера для этого выполнения + + Отключает индикатор хода выполнения и вертушку + Не удается сбросить {0}. Этот параметр управляется политикой. Для получения дополнительных сведений обратитесь к системному администратору. {Locked="{0}"} The value will be replaced with the feature name @@ -2908,10 +2968,11 @@ Исключить источник из обнаружения (true или false) - Возрастные ограничения + Явный + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. - Уровень доверия источника (нет или недоверенный) + Уровень доверия источника (нет или доверенный) Уровень доверия @@ -3184,17 +3245,19 @@ Семейство + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - Лица - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + Начертания + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - Фильтровать результаты по фамилии + Фильтровать результаты по имени семейства шрифтов + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - Лицо - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + Начертание + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. Пути @@ -3219,8 +3282,8 @@ Установщику пакета требуется проверка подлинности. При необходимости может появиться запрос проверки подлинности. Сведения, прошедшие проверку подлинности, будут предоставлены с URL-адресом скачивания установщика. - Не удалось скачать установщик. Эта winget не поддерживает метод проверки подлинности скачивания установщиком. Попробуйте обновиться до последней winget версии. - {Locked="winget"} + Не удалось скачать установщик. Эта версия WinGet не поддерживает способ проверки подлинности скачивания установщика. Попробуйте обновить до последней версии WinGet. + {Locked="WinGet"} Не удалось скачать установщик. Проверка подлинности не пройдена. @@ -3228,12 +3291,41 @@ Укажите путь к процессору конфигурации + + Настраиваемый путь к процессору: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Хэш: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Тип: псевдоним выполнения приложения + Indicates the processor path is an app execution alias (MSIX package link). + + + Путь: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Кем подписано: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Кем подписано: (не подписано) + Indicates the custom processor executable does not have an Authenticode signature. + + + Проверка целостности пользовательского пути к обработчику DSC не пройдена. Возможно, путь был изменен. + Error when the server-side hash of the processor path does not match the client-computed hash. + Команды ресурсов DSC v3 DSC stands for "Desired State Configuration". It should already have a locked translation. - Вложенные команды реализуют Desired State Configuration (DSC) версии 3 для настройки winget и пакетов. + Подкоманды здесь реализуют ресурсы Desired State Configuration (DSC) версии 3 для настройки WinGet и пакетов. + {Locked="WinGet"} Получить состояние ресурса @@ -3272,7 +3364,8 @@ Управление состоянием пакета - Управление пакетами через winget. + Управление пакетами через WinGet. + {Locked="WinGet"} Указывает, существует экземпляр или должен существовать. @@ -3308,7 +3401,8 @@ Управление файлом настроек пользователя - Управление настройками пользователя winget. + Управление настройками пользователя WinGet. + {Locked="WinGet"} Содержимое файла настроек JSON. @@ -3323,7 +3417,8 @@ Управление конфигурацией источника - Управляйте источниками winget. + Управление источниками WinGet. + {Locked="WinGet"} Имя, используемое для источника. @@ -3383,7 +3478,8 @@ Управление настройками администратора - Управление параметрами администратора winget. + Управление настройками администратора WinGet. + {Locked="WinGet"} Сбрасывает все настройки администратора @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. Новое значение Column title for listing the new value. + + Приоритет; сначала более высокие значения + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Приоритет + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + Приоритет источника. Более высокие значения идут первыми при сортировке. + + + Результаты отфильтрованы по приоритету самого высокоприоритетного сопоставленного источника. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + Предоставленный хэш обработчика DSC не совпадает с хэшем целевого файла. + \ No newline at end of file diff --git a/Localization/Resources/zh-CN/winget.resw b/Localization/Resources/zh-CN/winget.resw index daac4613f8..5f9840cf73 100644 --- a/Localization/Resources/zh-CN/winget.resw +++ b/Localization/Resources/zh-CN/winget.resw @@ -214,15 +214,19 @@ 已禁用 + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. 已启用 + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. 功能 + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. 链接 + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. 以下实验性功能正在进行中。 @@ -231,9 +235,11 @@ 属性 + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. 状态 + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. 进行哈希处理的文件 @@ -271,8 +277,8 @@ Microsoft 对第三方程序包概不负责,也不向第三方程序包授予任何许可证。 - 此程序包是通过 Microsoft Store 提供的。winget 可能需要代表当前用户从 Microsoft Store 获取该程序包。 - {Locked="winget"} + 此程序包是通过 Microsoft Store 提供的。WinGet 可能需要代表当前用户从 Microsoft Store 获取此程序包。 + {Locked="WinGet"} 安装选定的程序包(通过搜索配置的源或直接从清单中找到)。默认情况下,查询必须以不区分大小写的方式匹配程序包的 id、name 或 moniker。可以通过传递相应的选项来使用其他字段。默认情况下,安装命令将检查包安装状态,并尝试执行升级(如果适用)。使用 --force 重写以执行直接安装。 @@ -323,8 +329,8 @@ {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - Winget 目录 - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + WinGet 目录 + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs 要使用的区域设置(BCP47 格式) @@ -351,7 +357,7 @@ 日志位置(如果支持) - 版权所有 (C) Microsoft Corporation。保留所有权利。 + © 2026 Microsoft。保留所有权利。 主页 @@ -468,12 +474,14 @@ 匹配 + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. 名称 + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. 由于结果限制而截断了其他条目 @@ -497,6 +505,7 @@ 频道 + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. 显示有关特定程序包的信息。默认情况下,查询必须以不区分大小写的方式匹配程序包的 ID、名称或名字对象。可通过传递相应的选项来使用其他字段。 @@ -515,6 +524,18 @@ 只能在单个 - 后出现单个字符别名: "{0}" {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + 按属性对结果排序(可重复) + Description for the --sort argument used to sort output by field name. + + + 按升序对结果进行排序 + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + 按降序对结果进行排序 + Description for the --descending (--desc) flag that sets descending sort direction for output. + 具有给定名称的源已存在,并且引用了其他位置: @@ -616,8 +637,8 @@ {Locked="source reset"} - 打开预定义源失败; 请向 winget 维护人员报告。 - {Locked="winget"} + 无法打开预定义源;请向 WinGet 维护人员报告。 + {Locked="WinGet"} 正在删除所有源... @@ -672,7 +693,8 @@ 按标签筛选 - 感谢你使用 winget + 感谢你使用 WinGet + {Locked="WinGet"} 第三方声明 @@ -771,6 +793,7 @@ 已找到 + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. 路径是一个目录: {0} @@ -814,8 +837,8 @@ 已成功卸载 - winget 找不到此程序包的卸载命令。请与程序包发布者联系以获取支持。 - {Locked="winget"} + WinGet 找不到此包的卸载命令。请联系包发布者以获取支持。 + {Locked="WinGet"} 卸载已放弃 @@ -998,6 +1021,7 @@ 外部 + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". 在此导入中找到的包具有以下依赖项: @@ -1120,7 +1144,7 @@ 许可证 URL: - 绰号: + 绰号: 主页: @@ -1344,6 +1368,10 @@ 找到了较新的版本,但安装技术与当前安装的版本不同。请卸载包并安装较新的版本。 + + {0} 包() 阻止升级,因为较新的版本使用的安装技术与当前安装不同。卸载每个包,然后安装较新的版本。 + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + 指定的较新版本的安装技术与当前安装的版本不同。请卸载包并安装较新的版本。 @@ -1462,6 +1490,7 @@ 无法清除安装目录,因为它不是由 WinGet 创建的 + {Locked="WinGet"} 相关链接 @@ -1724,7 +1753,8 @@ {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - 无法使用 winget 升级包。请使用发布者提供的方法升级此包。 + 无法使用 WinGet 升级包。请使用发布者提供的方法来升级此包。 + {Locked="WinGet"} 即使程序包拥有非阻止性包钉,也要升级程序包 @@ -2021,6 +2051,12 @@ 跳过处理包依赖项和 Windows 功能 + + 仅安装包的依赖项 + + + 仅安装依赖项。不会安装包本身。 + 已跳过依赖项。 @@ -2111,7 +2147,8 @@ 启用 Windows 程序包管理器配置 - 检索有关错误的信息。给定一个数字,输出将包含有关错误的详细信息,如果是 winget 特定的错误,则包括符号名称。给定一个字符串,将搜索 winget 特定的错误以查找此值。 + 检索有关错误的信息。给定一个数字,输出将包含有关错误的详细信息,如果它是特定于 WinGet 的错误,则包括符号名称。给定一个字符串,搜索 WinGet 特定错误以查找此值。 + {Locked="WinGet"} 获取错误相关信息 @@ -2119,6 +2156,18 @@ 要在错误信息中搜索的值 + + 生成错误代码 Markdown 文件 + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + --output 参数不能与输入值结合使用。 + {Locked="--output"} + + + 必须提供输入值或 --output。 + {Locked="--output"} + 给定的数字太大,无法为 HRESULT。 @@ -2210,7 +2259,8 @@ Microsoft Store 应用遭到策略阻止 - 该功能当前正在开发中。可以使用 winget 设置启用它。 + 该功能当前正在开发中。可以使用 `winget settings` 启用它。 + {Locked="winget settings"} 未能安装 Microsoft Store 应用 @@ -2254,6 +2304,9 @@ 找不到适用的更新 + + 有可用升级,但使用的安装技术与当前安装不同 + winget upgrade --all 完成,但出现故障 @@ -2780,8 +2833,8 @@ 指定用于身份验证的帐户 - 无法添加源。此 winget 版本不支持源的身份验证方法。请尝试升级到最新 winget 版本。 - {Locked="winget"} + 未能添加源。此 WinGet 版本不支持源的身份验证方法。请尝试升级到最新的 WinGet 版本。 + {Locked="WinGet"} {0} 源需要身份验证。必要时可能会显示身份验证提示。将与源共享经过身份验证的信息以进行访问授权。 @@ -2861,12 +2914,19 @@ 启用Windows 程序包管理器代理命令行选项 Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + 启用Windows 程序包管理器配置处理器路径 + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + 设置要用于此执行的代理 禁止对此执行使用代理 + + 禁用进度栏和旋转图标 + 无法重置 {0}。此设置由策略控制。有关详细信息,请与系统管理员联系。 {Locked="{0}"} The value will be replaced with the feature name @@ -2909,6 +2969,7 @@ 显式 + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. 源的信任级别 (无或受信任的) @@ -3184,17 +3245,19 @@ 家庭 + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. 按家庭名称筛选结果 + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. 路径 @@ -3219,8 +3282,8 @@ 包安装程序需要身份验证。必要时可能会显示身份验证提示。将与安装程序下载 URL 共享经过身份验证的信息。 - 无法下载安装程序。此 winget 版本不支持安装程序下载身份验证方法。请尝试升级到最新 winget 版本。 - {Locked="winget"} + 未能下载安装程序。此 WinGet 版本不支持安装程序下载身份验证方法。请尝试升级到最新的 WinGet 版本。 + {Locked="WinGet"} 未能下载安装程序。身份验证失败。 @@ -3228,12 +3291,41 @@ 指定配置处理器的路径 + + 自定义处理器路径: + Displayed as a warning header when a custom --processor-path argument is provided. + + + 哈希: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + 类型: 应用执行别名 + Indicates the processor path is an app execution alias (MSIX package link). + + + 路径: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + 签名者: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + 签名者: (未签名) + Indicates the custom processor executable does not have an Authenticode signature. + + + 自定义 DSC 处理器路径的完整性检查失败。路径可能已被修改。 + Error when the server-side hash of the processor path does not match the client-computed hash. + DSC v3 资源命令 DSC stands for "Desired State Configuration". It should already have a locked translation. - 此处的子命令实现Desired State Configuration (DSC) v3 资源来配置 winget 和包。 + 此处的子命令实现Desired State Configuration (DSC) v3 资源,以配置 WinGet 和包。 + {Locked="WinGet"} 获取资源状态 @@ -3272,7 +3364,8 @@ 管理包状态 - 通过 winget 管理包。 + 通过 WinGet 管理程序包。 + {Locked="WinGet"} 指示实例是否应存在。 @@ -3308,7 +3401,8 @@ 管理用户设置文件 - 管理 winget 的用户设置。 + 管理 WinGet 的用户设置。 + {Locked="WinGet"} 设置 json 文件内容。 @@ -3323,7 +3417,8 @@ 管理源配置 - 管理 winget 的源。 + 管理 WinGet 的源。 + {Locked="WinGet"} 用于源的名称。 @@ -3383,7 +3478,8 @@ 管理管理员设置 - 管理 winget 的管理员设置。 + 管理 WinGet 的管理员设置。 + {Locked="WinGet"} 重置所有管理员设置 @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. 新值 Column title for listing the new value. + + 优先级;数字越大越靠前 + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + 优先级 + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + 源的优先级。顺序中较高的值排在最前面。 + + + 结果已筛选为最高匹配源优先级。 + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + 提供的 DSC 处理器哈希与目标文件的哈希不匹配。 + \ No newline at end of file diff --git a/Localization/Resources/zh-TW/winget.resw b/Localization/Resources/zh-TW/winget.resw index 70678b97db..bb28fd7d5e 100644 --- a/Localization/Resources/zh-TW/winget.resw +++ b/Localization/Resources/zh-TW/winget.resw @@ -214,15 +214,19 @@ 已停用 + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. 已啟用 + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. 功能 + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. 連結 + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. 下列實驗性功能正在運行中。 @@ -231,9 +235,11 @@ 屬性 + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. 狀態 + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. 雜湊的檔案 @@ -271,8 +277,8 @@ Microsoft 不負任何責任,也不會授與協力廠商封裝的任何授權。 - 此套件是透過 Microsoft Store 所提供。winget 可能需要代表目前的使用者從 Microsoft Store 取得套件。 - {Locked="winget"} + 此套件是透過 Microsoft Store 所提供。WinGet 可能需要代表目前使用者從 Microsoft Store 取得套件。 + {Locked="WinGet"} 安裝透過搜尋已設定來源或直接來自資訊清單的已選取套件。根據預設,査詢必須與套件的識別碼 (id)、名稱 (name) 或連結路徑 (moniker) 不區分大小寫。透過傳遞適當的選項,可以使用其他欄位。根據預設,install 命令將檢查套件的安裝狀態,並在適用的情况下嘗試執行升級。使用 --force 覆寫可執行直接安裝。 @@ -323,8 +329,8 @@ {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - Winget 目錄 - Header for a table detailing the directories Winget uses for key operations like logging and portable installs + WinGet 目錄 + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs 要使用的地區設定 (BCP47 格式) @@ -351,7 +357,7 @@ 記錄位置 (如果有支援的話) - Copyright (c) Microsoft Corporation. 著作權所有,並保留一切權利。 + © 2026 Microsoft.著作權所有,並保留一切權利。 首頁 @@ -468,12 +474,14 @@ 相符 + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. 名稱 來源 + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. 由於結果限制,已截斷其他項目 @@ -497,6 +505,7 @@ 頻道 + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. 顯示特定封裝的資訊。根據預設,查詢必須 insensitively 符合封裝的識別碼、名稱或名字物件。您可以透過傳遞適當的選項來使用其他欄位。 @@ -515,6 +524,18 @@ 單一 - 後只能出現單一字元別名: [{0}] {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + 依屬性排序結果 (可重複) + Description for the --sort argument used to sort output by field name. + + + 將結果以遞增順序排序 + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + 將結果以遞減順序排序 + Description for the --descending (--desc) flag that sets descending sort direction for output. + 具有給定名稱的來源已存在,且參照到不同的位置: @@ -616,8 +637,8 @@ {Locked="source reset"} - 無法開啟預先定義的來源; 請向 winget 維護者回報。 - {Locked="winget"} + 無法開啟預先定義的來源; 請向 WinGet 維護者回報。 + {Locked="WinGet"} 正在移除所有來源... @@ -672,7 +693,8 @@ 依標籤篩選結果 - 感謝您使用 Winget + 感謝您使用 WinGet + {Locked="WinGet"} 第三方聲明 @@ -771,6 +793,7 @@ 找到 + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. 路徑為目錄; {0} @@ -814,8 +837,8 @@ 已成功解除安裝 - winget 找不到此套件的解除安裝命令。請向套件發行者尋求支援。 - {Locked="winget"} + WinGet 找不到此套件的解除安裝命令。請向套件發行者尋求支援。 + {Locked="WinGet"} 已放棄解除安裝 @@ -998,6 +1021,7 @@ 外部 + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". 在此匯入中找到的套件具有下列相依性: @@ -1344,6 +1368,10 @@ 找到較新的版本,但安裝技術與目前安裝的版本不同。請卸載套件並安裝較新的版本。 + + {0} 套件() 已封鎖升級,因為較新的版本使用與目前安裝不同的安裝技術。解除安裝每個套件,然後安裝較新的版本。 + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + 指定之較新版本的安裝技術與目前安裝的版本不同。請卸載套件並安裝較新的版本。 @@ -1462,6 +1490,7 @@ 無法清除安裝目錄,因為它不是由 WinGet 所建立 + {Locked="WinGet"} 相關連結 @@ -1724,7 +1753,8 @@ {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - 無法使用 winget 升級封裝。請使用發行者提供的方法來升級此套件。 + 無法使用 WinGet 升級封裝。請使用發行者提供的方法來升級此套件。 + {Locked="WinGet"} 即使有非封鎖釘選仍要更新套件 @@ -2021,6 +2051,12 @@ 略過處理封裝相依性和 Windows 功能 + + 僅安裝套件的相依性 + + + 僅安裝相依性。將不會安裝套件本身。 + 已略過相依性。 @@ -2111,7 +2147,8 @@ 啟用 Windows 封裝管理員設定 - 擷取錯誤的相關信息。指定數位后,輸出將包含錯誤的詳細數據,包括符號名稱,如果是 Winget 特定的錯誤。指定字串時,會搜尋 winget 特定錯誤以尋找此值。 + 擷取錯誤相關資訊。給定一個數字,輸出將包含有關錯誤的詳細信息,如果它是 WinGet 特定錯誤,則包括符號名稱。給定字串,會搜尋此值的 WinGet 特定錯誤。 + {Locked="WinGet"} 取得關於錯誤的資訊 @@ -2119,6 +2156,18 @@ 要在錯誤資訊中搜尋的值 + + 產生錯誤碼 Markdown 檔案 + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + --output 引數不能與輸入值合併。 + {Locked="--output"} + + + 必須提供輸入值或 --output。 + {Locked="--output"} + 指定的數位太大,無法成為 HRESULT。 @@ -2210,7 +2259,8 @@ 原則已封鎖 Microsoft Store 應用程式 - 功能目前正在開發中。您可以使用 winget 設定來啟用它。 + 此功能目前正在開發中。可以使用 `winget settings` 啟用它。 + {Locked="winget settings"} 安裝 Microsoft Store 應用程式失敗 @@ -2254,6 +2304,9 @@ 找不到適用的更新 + + 有可用的升級,但其安裝技術與目前的安裝不同 + winget 升級 -- 全部完成,但有失敗部分 @@ -2780,8 +2833,8 @@ 指定要用於驗證的帳戶 - 無法新增來源。此 winget 版本不支援來源的驗證方法。嘗試升級至最新 winget 版本。 - {Locked="winget"} + 無法新增來源。此 WinGet 版本不支援來源的驗證方法。請嘗試升級至最新的 WinGet 版本。 + {Locked="WinGet"} {0} 來源需要驗證。必要時可能會顯示驗證提示。已驗證的資訊將與來源共用以取得存取授權。 @@ -2861,12 +2914,19 @@ 啟用Windows 封裝管理員 Proxy 命令行選項 Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + 啟用 Windows 套件管理員設定處理器路徑 + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + 設定要用於此執行的 Proxy 停用此執行的 Proxy 使用 + + 停用進度列和進度環 + 無法重設 {0}。這個設定是由原則控制。如需詳細資訊,請連絡您的系統管理員。 {Locked="{0}"} The value will be replaced with the feature name @@ -2908,7 +2968,8 @@ 從探索中排除來源 (true 或 false) - 偏激 + 明確指定 + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. 來源的信任層級 (無或信任) @@ -3184,17 +3245,19 @@ 家庭 + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - 表情 - "Faces" represents the typeface of the font family such as 'Bold' or 'Italic' + 字體 + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - 依家庭名稱篩選結果 + 依字型家族名稱篩選結果 + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - - "Face" represents the typeface of a font family such as 'Bold' or 'Italic' + 字體 + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. 路徑 @@ -3219,8 +3282,8 @@ 套件安裝程式需要驗證。必要時可能會顯示驗證提示。已驗證的資訊將會與安裝程序下載 URL 共用。 - 無法下載安裝程式。此 winget 版本不支援安裝程式下載驗證方法。嘗試升級至最新 winget 版本。 - {Locked="winget"} + 無法下載安裝程式。此 WinGet 版本不支援安裝程式下載驗證方法。請嘗試升級至最新的 WinGet 版本。 + {Locked="WinGet"} 無法下載安裝程式。驗證失敗。 @@ -3228,12 +3291,41 @@ 指定設定處理器的路徑 + + 自訂處理器路徑: + Displayed as a warning header when a custom --processor-path argument is provided. + + + 雜湊: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + 類型: 應用程式執行別名 + Indicates the processor path is an app execution alias (MSIX package link). + + + 路徑: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + 簽名者: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + 簽署者: (未簽署) + Indicates the custom processor executable does not have an Authenticode signature. + + + 自訂 DSC 處理器路徑的完整性檢查失敗。路徑可能已被修改。 + Error when the server-side hash of the processor path does not match the client-computed hash. + DSC v3 資源命令 DSC stands for "Desired State Configuration". It should already have a locked translation. - 這裡的子命令實作 Desired State Configuration (DSC) v3 資源,以設定 winget 和封裝。 + 此處的子命令會實作Desired State Configuration (DSC) v3 資源,以設定 WinGet 和套件。 + {Locked="WinGet"} 取得資源狀態 @@ -3272,7 +3364,8 @@ 管理套件狀態 - 透過 winget 管理套件。 + 透過 WinGet 管理套件。 + {Locked="WinGet"} 指出實例是否應存在。 @@ -3308,7 +3401,8 @@ 管理使用者設定檔案 - 管理 winget 的使用者設定。 + 管理 WinGet 的使用者設定。 + {Locked="WinGet"} 設定 JSON 檔案內容。 @@ -3323,7 +3417,8 @@ 管理來源設定 - 管理 winget 的來源。 + 管理 WinGet 的來源。 + {Locked="WinGet"} 要用於來源的名稱。 @@ -3383,7 +3478,8 @@ 管理系統管理員設定 - 管理 winget 的系統管理員設定。 + 管理 WinGet 的系統管理員設定。 + {Locked="WinGet"} 重設所有系統管理員設定 @@ -3535,4 +3631,22 @@ An unlocalized JSON fragment will follow on another line. 新值 Column title for listing the new value. + + 優先順序; 數字越大,優先順序越高 + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + 優先順序 + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + 來源的優先順序。較高的值會依順序排在最前面。 + + + 結果已篩選為最高相符來源優先順序。 + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + 提供的 DSC 處理器雜湊與目標檔案的雜湊不符。 + \ No newline at end of file From 0c7fc5825d92115e4f3368461cf4bb4706e5658c Mon Sep 17 00:00:00 2001 From: Tom Plant Date: Fri, 15 May 2026 21:15:05 +1000 Subject: [PATCH 14/18] Run GitHub Copilot cloud agent on Windows --- .github/workflows/copilot-setup-steps.yml | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000000..daee628f88 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,24 @@ +name: "Copilot Setup Steps" + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: windows-latest + + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. + # If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read From 50514bf15c53d109976243830bffca2412b8a9db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 11:19:17 +0000 Subject: [PATCH 15/18] Initial plan From 4125656c79a1f0f9b42a4f95b2266a30f3666105 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 11:25:40 +0000 Subject: [PATCH 16/18] Add LoggingUseCMTrace setting with CMTrace log format support and tests Agent-Logs-Url: https://github.com/devicie/winget-cli/sessions/c4d184c0-b0f2-46bc-9e42-e68d558e7f8a Co-authored-by: pl4nty <21111317+pl4nty@users.noreply.github.com> --- src/AppInstallerCLITests/UserSettings.cpp | 40 +++++++++++++ src/AppInstallerCommonCore/FileLogger.cpp | 57 ++++++++++++++++++- .../Public/winget/UserSettings.h | 2 + src/AppInstallerCommonCore/UserSettings.cpp | 1 + 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp index b1b28519b3..3bb213d957 100644 --- a/src/AppInstallerCLITests/UserSettings.cpp +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -925,6 +925,46 @@ TEST_CASE("SettingOutputSortDirection", "[settings]") } } +TEST_CASE("SettingLoggingUseCMTrace", "[settings]") +{ + auto again = DeleteUserSettingsFiles(); + + SECTION("Default value") + { + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == false); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Enabled") + { + std::string_view json = R"({ "logging": { "useCMTrace": true } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == true); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Disabled") + { + std::string_view json = R"({ "logging": { "useCMTrace": false } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == false); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Bad value type") + { + std::string_view json = R"({ "logging": { "useCMTrace": "yes" } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == false); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } +} + TEST_CASE("ConvertToSortField", "[settings]") { SECTION("Valid values - lowercase") diff --git a/src/AppInstallerCommonCore/FileLogger.cpp b/src/AppInstallerCommonCore/FileLogger.cpp index a430694ef6..14751b8909 100644 --- a/src/AppInstallerCommonCore/FileLogger.cpp +++ b/src/AppInstallerCommonCore/FileLogger.cpp @@ -29,6 +29,53 @@ namespace AppInstaller::Logging return std::move(strstr).str(); } + // Formats a log line in CMTrace format. + // CMTrace log format: + std::string ToCMTraceLogLine(Channel channel, Level level, std::string_view message) + { + auto now = std::chrono::system_clock::now(); + auto tt = std::chrono::system_clock::to_time_t(now); + tm localTime{}; + _localtime64_s(&localTime, &tt); + + auto sinceEpoch = now.time_since_epoch(); + auto leftoverMillis = std::chrono::duration_cast(sinceEpoch) - std::chrono::duration_cast(sinceEpoch); + + // Get UTC bias in minutes (positive means west of UTC, CMTrace uses positive for west) + long timezoneBiasSeconds = 0; + _get_timezone(&timezoneBiasSeconds); + long biasMins = timezoneBiasSeconds / 60; + + // CMTrace type: 1=Info/Verbose, 2=Warning, 3=Error/Critical + int type; + switch (level) + { + case Level::Warning: type = 2; break; + case Level::Error: + case Level::Crit: type = 3; break; + default: type = 1; break; + } + + std::stringstream strstr; + strstr << "" + << ""; + return std::move(strstr).str(); + } + // Determines the difference between the given position and the maximum as an offset. std::ofstream::off_type CalculateDiff(const std::ofstream::pos_type& position, std::ofstream::off_type maximum) { @@ -94,7 +141,15 @@ namespace AppInstaller::Logging void FileLogger::Write(Channel channel, Level level, std::string_view message) noexcept try { - std::string log = ToLogLine(channel, level, message); + std::string log; + if (Settings::User().Get()) + { + log = ToCMTraceLogLine(channel, level, message); + } + else + { + log = ToLogLine(channel, level, message); + } WriteDirect(channel, level, log); } catch (...) {} diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index 9909d9acd3..fe82dd294d 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -133,6 +133,7 @@ namespace AppInstaller::Settings LoggingFileTotalSizeLimitInMB, LoggingFileIndividualSizeLimitInMB, LoggingFileCountLimit, + LoggingUseCMTrace, // Uninstall behavior UninstallPurgePortablePackage, // Download behavior @@ -237,6 +238,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileTotalSizeLimitInMB, uint32_t, uint32_t, 128, ".logging.file.totalSizeLimitInMB"sv); SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileIndividualSizeLimitInMB, uint32_t, uint32_t, 16, ".logging.file.individualSizeLimitInMB"sv); SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileCountLimit, uint32_t, uint32_t, 0, ".logging.file.countLimit"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingUseCMTrace, bool, bool, false, ".logging.useCMTrace"sv); // Interactivity SETTINGMAPPING_SPECIALIZATION(Setting::InteractivityDisable, bool, bool, false, ".interactivity.disable"sv); // Output behavior diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 38cdd0847a..203e2a216c 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -299,6 +299,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(LoggingFileTotalSizeLimitInMB) WINGET_VALIDATE_PASS_THROUGH(LoggingFileIndividualSizeLimitInMB) WINGET_VALIDATE_PASS_THROUGH(LoggingFileCountLimit) + WINGET_VALIDATE_PASS_THROUGH(LoggingUseCMTrace) #ifndef AICLI_DISABLE_TEST_HOOKS WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) From ad9fd589a5e355b6a4409c477050792209006094 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 07:09:13 +0000 Subject: [PATCH 17/18] Rename CMTrace to CCM, change bool setting to logging.format enum, add build workflow Agent-Logs-Url: https://github.com/devicie/winget-cli/sessions/7d39e80e-abcc-4238-8301-b2a0f059dcaf Co-authored-by: pl4nty <21111317+pl4nty@users.noreply.github.com> --- .github/workflows/build-msix.yml | 75 +++++++++++++++++++ src/AppInstallerCLITests/UserSettings.cpp | 38 +++++++--- src/AppInstallerCommonCore/FileLogger.cpp | 12 +-- .../Public/winget/UserSettings.h | 4 +- src/AppInstallerCommonCore/UserSettings.cpp | 17 ++++- .../Public/AppInstallerLogging.h | 9 +++ 6 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/build-msix.yml diff --git a/.github/workflows/build-msix.yml b/.github/workflows/build-msix.yml new file mode 100644 index 0000000000..94220a9bd2 --- /dev/null +++ b/.github/workflows/build-msix.yml @@ -0,0 +1,75 @@ +name: Build MSIX + +on: + workflow_dispatch: + push: + branches: + - 'copilot/**' + paths: + - 'src/**' + - '.github/workflows/build-msix.yml' + pull_request: + paths: + - 'src/**' + - '.github/workflows/build-msix.yml' + +jobs: + build: + runs-on: windows-latest + timeout-minutes: 120 + + permissions: + contents: read + + strategy: + matrix: + platform: [x64] + configuration: [Release] + + env: + solution: 'src\AppInstallerCLI.sln' + buildOutDir: ${{ github.workspace }}\src\${{ matrix.platform }}\${{ matrix.configuration }} + appxPackageDir: ${{ github.workspace }}\artifacts\${{ matrix.platform }}\AppxPackages + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install NuGet + uses: NuGet/setup-nuget@v2 + + - name: Restore Solution (NuGet) + run: nuget restore ${{ env.solution }} + + - name: Restore AppInstallerCLIPackage (NuGet) + run: nuget restore src\AppInstallerCLIPackage\AppInstallerCLIPackage.wapproj + + - name: Restore .NET projects + run: dotnet restore src + + - name: Integrate vcpkg + run: | + & "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" integrate install + shell: pwsh + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v2 + + - name: Build Solution + run: | + msbuild ${{ env.solution }} ` + /p:Platform=${{ matrix.platform }} ` + /p:Configuration=${{ matrix.configuration }} ` + /p:AppxBundlePlatforms="${{ matrix.platform }}" ` + /p:AppxPackageDir="${{ env.appxPackageDir }}" ` + /p:AppxBundle=Always ` + /p:UapAppxPackageBuildMode=SideloadOnly ` + /m + shell: pwsh + + - name: Upload MSIX artifacts + uses: actions/upload-artifact@v4 + with: + name: msix-${{ matrix.platform }}-${{ matrix.configuration }} + path: ${{ env.appxPackageDir }} + retention-days: 7 diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp index 3bb213d957..1063a810d3 100644 --- a/src/AppInstallerCLITests/UserSettings.cpp +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -925,7 +925,7 @@ TEST_CASE("SettingOutputSortDirection", "[settings]") } } -TEST_CASE("SettingLoggingUseCMTrace", "[settings]") +TEST_CASE("SettingLoggingFormat", "[settings]") { auto again = DeleteUserSettingsFiles(); @@ -933,34 +933,52 @@ TEST_CASE("SettingLoggingUseCMTrace", "[settings]") { UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == false); + REQUIRE(userSettingTest.Get() == LogFileFormat::WinGet); REQUIRE(userSettingTest.GetWarnings().size() == 0); } - SECTION("Enabled") + SECTION("WinGet") { - std::string_view json = R"({ "logging": { "useCMTrace": true } })"; + std::string_view json = R"({ "logging": { "format": "winget" } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == true); + REQUIRE(userSettingTest.Get() == LogFileFormat::WinGet); REQUIRE(userSettingTest.GetWarnings().size() == 0); } - SECTION("Disabled") + SECTION("CCM") { - std::string_view json = R"({ "logging": { "useCMTrace": false } })"; + std::string_view json = R"({ "logging": { "format": "ccm" } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == false); + REQUIRE(userSettingTest.Get() == LogFileFormat::CCM); REQUIRE(userSettingTest.GetWarnings().size() == 0); } + SECTION("Case insensitive CCM") + { + std::string_view json = R"({ "logging": { "format": "CCM" } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == LogFileFormat::CCM); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Bad value") + { + std::string_view json = R"({ "logging": { "format": "cmtrace" } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == LogFileFormat::WinGet); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } SECTION("Bad value type") { - std::string_view json = R"({ "logging": { "useCMTrace": "yes" } })"; + std::string_view json = R"({ "logging": { "format": true } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == false); + REQUIRE(userSettingTest.Get() == LogFileFormat::WinGet); REQUIRE(userSettingTest.GetWarnings().size() == 1); } } diff --git a/src/AppInstallerCommonCore/FileLogger.cpp b/src/AppInstallerCommonCore/FileLogger.cpp index 14751b8909..aefd8f97d9 100644 --- a/src/AppInstallerCommonCore/FileLogger.cpp +++ b/src/AppInstallerCommonCore/FileLogger.cpp @@ -29,9 +29,9 @@ namespace AppInstaller::Logging return std::move(strstr).str(); } - // Formats a log line in CMTrace format. - // CMTrace log format: - std::string ToCMTraceLogLine(Channel channel, Level level, std::string_view message) + // Formats a log line in CCM (CMTrace-compatible) format. + // CCM log format: + std::string ToCCMLogLine(Channel channel, Level level, std::string_view message) { auto now = std::chrono::system_clock::now(); auto tt = std::chrono::system_clock::to_time_t(now); @@ -46,7 +46,7 @@ namespace AppInstaller::Logging _get_timezone(&timezoneBiasSeconds); long biasMins = timezoneBiasSeconds / 60; - // CMTrace type: 1=Info/Verbose, 2=Warning, 3=Error/Critical + // CCM type: 1=Info/Verbose, 2=Warning, 3=Error/Critical int type; switch (level) { @@ -142,9 +142,9 @@ namespace AppInstaller::Logging void FileLogger::Write(Channel channel, Level level, std::string_view message) noexcept try { std::string log; - if (Settings::User().Get()) + if (Settings::User().Get() == LogFileFormat::CCM) { - log = ToCMTraceLogLine(channel, level, message); + log = ToCCMLogLine(channel, level, message); } else { diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index fe82dd294d..2761367192 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -133,7 +133,7 @@ namespace AppInstaller::Settings LoggingFileTotalSizeLimitInMB, LoggingFileIndividualSizeLimitInMB, LoggingFileCountLimit, - LoggingUseCMTrace, + LoggingFormat, // Uninstall behavior UninstallPurgePortablePackage, // Download behavior @@ -238,7 +238,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileTotalSizeLimitInMB, uint32_t, uint32_t, 128, ".logging.file.totalSizeLimitInMB"sv); SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileIndividualSizeLimitInMB, uint32_t, uint32_t, 16, ".logging.file.individualSizeLimitInMB"sv); SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileCountLimit, uint32_t, uint32_t, 0, ".logging.file.countLimit"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingUseCMTrace, bool, bool, false, ".logging.useCMTrace"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFormat, std::string, Logging::LogFileFormat, Logging::LogFileFormat::WinGet, ".logging.format"sv); // Interactivity SETTINGMAPPING_SPECIALIZATION(Setting::InteractivityDisable, bool, bool, false, ".interactivity.disable"sv); // Output behavior diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 203e2a216c..7c3272abe5 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -299,7 +299,6 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(LoggingFileTotalSizeLimitInMB) WINGET_VALIDATE_PASS_THROUGH(LoggingFileIndividualSizeLimitInMB) WINGET_VALIDATE_PASS_THROUGH(LoggingFileCountLimit) - WINGET_VALIDATE_PASS_THROUGH(LoggingUseCMTrace) #ifndef AICLI_DISABLE_TEST_HOOKS WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) @@ -531,6 +530,22 @@ namespace AppInstaller::Settings return value * 24h; } + WINGET_VALIDATE_SIGNATURE(LoggingFormat) + { + static constexpr std::string_view s_format_winget = "winget"; + static constexpr std::string_view s_format_ccm = "ccm"; + + if (Utility::CaseInsensitiveEquals(value, s_format_winget)) + { + return LogFileFormat::WinGet; + } + else if (Utility::CaseInsensitiveEquals(value, s_format_ccm)) + { + return LogFileFormat::CCM; + } + return {}; + } + WINGET_VALIDATE_SIGNATURE(OutputSortOrder) { std::vector fields; diff --git a/src/AppInstallerSharedLib/Public/AppInstallerLogging.h b/src/AppInstallerSharedLib/Public/AppInstallerLogging.h index 39dbed0b6f..f7094e942e 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerLogging.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerLogging.h @@ -98,6 +98,15 @@ namespace AppInstaller::Logging ShortGuid, }; + // The format used when writing log entries to a file. + enum class LogFileFormat + { + // Default WinGet format: " [channel] message" + WinGet, + // CCM/CMTrace-compatible format recognized by CMTrace and Microsoft Endpoint Configuration Manager log viewers + CCM, + }; + // Indicates a location of significance in the logging stream. enum class Tag { From 046bef92f38736ad59da36119d9a5f33759fefab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 30 May 2026 02:19:17 +0000 Subject: [PATCH 18/18] Fix spelling check: add 'fff' (datetime milliseconds token) to expect.txt --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 6551758cbd..10ab13c0bf 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -179,6 +179,7 @@ fdw fdwgp FECAFEB fedorapeople +fff fileinuse filemode Filetime