winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 984b7c13c138a25bb587c74c3128e2d529059d6b
parent c17110b97e72079739e3425fd7a8fc22f2c16168
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date:   Fri, 24 Sep 2021 18:51:50 -0700

Add option to check fields requiring verified publisher in manifest p… (#1487)


Diffstat:
Msrc/AppInstallerCLICore/Commands/ValidateCommand.cpp | 8++++++--
Msrc/AppInstallerCLITests/TestData/InstallFlowTest_LicenseAgreement.yaml | 3++-
Msrc/AppInstallerCLITests/YamlManifest.cpp | 54++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp | 27+++++++++++++++++++++------
Msrc/AppInstallerCommonCore/Manifest/YamlParser.cpp | 49+++++++++++++++++++++----------------------------
Msrc/AppInstallerCommonCore/Public/winget/ManifestCommon.h | 10++++++++++
Msrc/AppInstallerCommonCore/Public/winget/ManifestValidation.h | 1+
Msrc/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h | 23++++++-----------------
Msrc/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h | 19++++++++++++++-----
Msrc/WinGetUtil/Exports.cpp | 18++++++++++++++----
Msrc/WinGetUtil/WinGetUtil.h | 7+++++--
Msrc/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp | 2+-
12 files changed, 145 insertions(+), 76 deletions(-)

diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp @@ -9,6 +9,7 @@ namespace AppInstaller::CLI { using namespace std::string_view_literals; + using namespace AppInstaller::Manifest; std::vector<Argument> ValidateCommand::GetArguments() const { @@ -42,7 +43,10 @@ namespace AppInstaller::CLI try { - auto manifest = Manifest::YamlParser::CreateFromPath(inputFile, true, true); + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + auto manifest = YamlParser::CreateFromPath(inputFile, validateOption); context.Add<Execution::Data::Manifest>(manifest); context << @@ -51,7 +55,7 @@ namespace AppInstaller::CLI context.Reporter.Info() << Resource::String::ManifestValidationSuccess << std::endl; } - catch (const Manifest::ManifestException& e) + catch (const ManifestException& e) { HRESULT hr = S_OK; if (e.IsWarningOnly()) diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_LicenseAgreement.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_LicenseAgreement.yaml @@ -1,10 +1,11 @@ -PackageIdentifier: TestInstallerWithLicenseAgreement +PackageIdentifier: TestInstaller.WithLicenseAgreement PackageVersion: 1.0.0.0 PackageLocale: en-US PackageName: AppInstaller Test Installer Publisher: Microsoft Corporation Moniker: AICLITestExe License: Test +ShortDescription: TestInstallerWithLicenseAgreement Agreements: - AgreementLabel: Agreement with text Agreement: This is the text of the agreement. diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -168,16 +168,33 @@ private: bool m_expectedWarningOnly; }; -void TestManifest(const std::filesystem::path& manifestPath, const std::string& expectedMessage = {}, bool expectedWarningOnly = false) +ManifestValidateOption GetTestManifestValidateOption( + bool schemaValidationOnly = false, + bool errorOnVerifiedPublisher = false) +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + validateOption.SchemaValidationOnly = schemaValidationOnly; + validateOption.ErrorOnVerifiedPublisherFields = errorOnVerifiedPublisher; + return validateOption; +} + +void TestManifest( + const std::filesystem::path& manifestPath, + const std::string& expectedMessage = {}, + bool expectedWarningOnly = false, + ManifestValidateOption validateOption = GetTestManifestValidateOption()) { INFO(manifestPath.u8string()); + if (expectedMessage.empty()) { - CHECK_NOTHROW(YamlParser::CreateFromPath(TestDataFile(manifestPath), true, true)); + CHECK_NOTHROW(YamlParser::CreateFromPath(TestDataFile(manifestPath), validateOption)); } else { - CHECK_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile(manifestPath), true, true), ManifestException, ManifestExceptionMatcher(expectedMessage, expectedWarningOnly)); + CHECK_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile(manifestPath), validateOption), ManifestException, ManifestExceptionMatcher(expectedMessage, expectedWarningOnly)); } } @@ -186,6 +203,7 @@ struct ManifestTestCase std::string TestFile; std::string ExpectedMessage = {}; bool IsWarningOnly = false; + ManifestValidateOption ValidateOption = GetTestManifestValidateOption(); }; TEST_CASE("ReadGoodManifests", "[ManifestValidation]") @@ -256,12 +274,14 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-ProductCodeOnMSIX.yaml", "The specified installer type does not support ProductCode. Field: InstallerType Value: Msix" }, { "Manifest-Bad-InvalidUpdateBehavior.yaml", "Invalid field value. Field: UpdateBehavior" }, { "Manifest-Bad-InvalidLocale.yaml", "The locale value is not a well formed bcp47 language tag." }, - { "Manifest-Bad-AppsAndFeaturesEntriesOnMSIX.yaml", "The specified installer type does not write to Apps and Features entry." } + { "Manifest-Bad-AppsAndFeaturesEntriesOnMSIX.yaml", "The specified installer type does not write to Apps and Features entry." }, + { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers.", true }, + { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers.", false, GetTestManifestValidateOption(false, true) }, }; for (auto const& testCase : TestCases) { - TestManifest(testCase.TestFile, testCase.ExpectedMessage, testCase.IsWarningOnly); + TestManifest(testCase.TestFile, testCase.ExpectedMessage, testCase.IsWarningOnly, testCase.ValidateOption); } } @@ -281,7 +301,7 @@ TEST_CASE("ManifestEncoding", "[ManifestValidation]") for (auto const& testCase : TestCases) { INFO(testCase.TestFile); - Manifest manifest = YamlParser::CreateFromPath(TestDataFile(testCase.TestFile), true, true); + Manifest manifest = YamlParser::CreateFromPath(TestDataFile(testCase.TestFile), GetTestManifestValidateOption()); REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageName>() == u8"MSIX SDK\xA9"); } } @@ -540,14 +560,26 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(localization1.Get<Localization::ShortDescription>() == "This is MSIX SDK UK"); REQUIRE(localization1.Get<Localization::Description>() == "The MSIX SDK project is an effort to enable developers UK"); REQUIRE(localization1.Get<Localization::Tags>() == MultiValue{ "appxsdkUK", "msixsdkUK" }); + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(localization1.Get<Localization::ReleaseNotes>() == "Release notes"); + REQUIRE(localization1.Get<Localization::ReleaseNotesUrl>() == "https://ReleaseNotes.net"); + REQUIRE(localization1.Get<Localization::Agreements>().size() == 1); + REQUIRE(localization1.Get<Localization::Agreements>().at(0).Label == "Label"); + REQUIRE(localization1.Get<Localization::Agreements>().at(0).AgreementText == "Text"); + REQUIRE(localization1.Get<Localization::Agreements>().at(0).AgreementUrl == "https://AgreementUrl.net"); + } } } TEST_CASE("ValidateV1GoodManifestAndVerifyContents", "[ManifestValidation]") { + ManifestValidateOption validateOption; + validateOption.FullValidation = true; TempDirectory singletonDirectory{ "SingletonManifest" }; CopyTestDataFilesToFolder({ "ManifestV1-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, true, true); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); VerifyV1ManifestContent(singletonManifest, true); TempDirectory multiFileDirectory{ "MultiFileManifest" }; @@ -558,7 +590,7 @@ TEST_CASE("ValidateV1GoodManifestAndVerifyContents", "[ManifestValidation]") "ManifestV1-MultiFile-Locale.yaml" }, multiFileDirectory); TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, true, true, mergedManifestFile); + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); VerifyV1ManifestContent(multiFileManifest, false); // Read from merged manifest should have the same content as multi file manifest @@ -568,9 +600,11 @@ TEST_CASE("ValidateV1GoodManifestAndVerifyContents", "[ManifestValidation]") TEST_CASE("ValidateV1_1GoodManifestAndVerifyContents", "[ManifestValidation]") { + ManifestValidateOption validateOption; + validateOption.FullValidation = true; TempDirectory singletonDirectory{ "SingletonManifest" }; CopyTestDataFilesToFolder({ "ManifestV1_1-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, true, true); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); VerifyV1ManifestContent(singletonManifest, true, ManifestVer{s_ManifestVersionV1_1}); TempDirectory multiFileDirectory{ "MultiFileManifest" }; @@ -581,7 +615,7 @@ TEST_CASE("ValidateV1_1GoodManifestAndVerifyContents", "[ManifestValidation]") "ManifestV1_1-MultiFile-Locale.yaml" }, multiFileDirectory); TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, true, true, mergedManifestFile); + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_1 }); // Read from merged manifest should have the same content as multi file manifest diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -392,7 +392,7 @@ namespace AppInstaller::Manifest { std::vector<FieldProcessInfo> fields_v1_1 = { - { "Agreements", [this](const YAML::Node& value)->ValidationErrors { return ProcessAgreementsNode(value); } }, + { "Agreements", [this](const YAML::Node& value)->ValidationErrors { return ProcessAgreementsNode(value); }, true }, { "ReleaseNotes", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add<Localization::ReleaseNotes>(value.as<std::string>()); return {}; } }, { "ReleaseNotesUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add<Localization::ReleaseNotesUrl>(value.as<std::string>()); return {}; } }, }; @@ -543,6 +543,13 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column); } + if (fieldInfo.RequireVerifiedPublisher) + { + resultErrors.emplace_back(ManifestError::FieldRequireVerifiedPublisher, fieldInfo.Name, "", + m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column, + m_validateOption.ErrorOnVerifiedPublisherFields ? ValidationError::Level::Error : ValidationError::Level::Warning); + } + if (!valueNode.IsNull()) { try @@ -559,7 +566,7 @@ namespace AppInstaller::Manifest else { // For full validation, also reports unrecognized fields as warning - if (m_fullValidation) + if (m_validateOption.FullValidation) { resultErrors.emplace_back(ManifestError::FieldUnknown, key, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column, ValidationError::Level::Warning); } @@ -664,9 +671,13 @@ namespace AppInstaller::Manifest return resultErrors; } - ValidationErrors ManifestYamlPopulator::PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation) + ValidationErrors ManifestYamlPopulator::PopulateManifestInternal( + const YAML::Node& rootNode, + Manifest& manifest, + const ManifestVer& manifestVersion, + ManifestValidateOption validateOption) { - m_fullValidation = fullValidation; + m_validateOption = validateOption; m_isMergedManifest = !rootNode["ManifestType"sv].IsNull() && rootNode["ManifestType"sv].as<std::string>() == "merged"; ValidationErrors resultErrors; @@ -772,9 +783,13 @@ namespace AppInstaller::Manifest return resultErrors; } - ValidationErrors ManifestYamlPopulator::PopulateManifest(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation) + ValidationErrors ManifestYamlPopulator::PopulateManifest( + const YAML::Node& rootNode, + Manifest& manifest, + const ManifestVer& manifestVersion, + ManifestValidateOption validateOption) { ManifestYamlPopulator manifestPopulator; - return manifestPopulator.PopulateManifestInternal(rootNode, manifest, manifestVersion, fullValidation); + return manifestPopulator.PopulateManifestInternal(rootNode, manifest, manifestVersion, validateOption); } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -86,7 +86,7 @@ namespace AppInstaller::Manifest::YamlParser // - Validate manifest type correctness // - Allowed file type in multi file manifest: version, installer, defaultLocale, locale // - Allowed file type in single file manifest: preview manifest, merged and singleton - ManifestVer ValidateInput(std::vector<YamlManifestInfo>& input, bool fullValidation, bool schemaValidationOnly) + ManifestVer ValidateInput(std::vector<YamlManifestInfo>& input, ManifestValidateOption validateOption) { std::vector<ValidationError> errors; @@ -253,7 +253,7 @@ namespace AppInstaller::Manifest::YamlParser errors.emplace_back(ManifestError::InconsistentMultiFileManifestDefaultLocale); } - if (!schemaValidationOnly && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) + if (!validateOption.SchemaValidationOnly && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) { errors.emplace_back(ManifestError::IncompleteMultiFileManifest); } @@ -264,12 +264,12 @@ namespace AppInstaller::Manifest::YamlParser ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); firstYamlManifest.ManifestType = manifestType; - if (fullValidation && manifestType == ManifestTypeEnum::Merged) + if (validateOption.FullValidation && manifestType == ManifestTypeEnum::Merged) { errors.emplace_back(ValidationError::MessageFieldValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); } - if (!schemaValidationOnly && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) + if (!validateOption.SchemaValidationOnly && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) { errors.emplace_back(ValidationError::MessageWithFile(ManifestError::IncompleteMultiFileManifest, firstYamlManifest.FileName)); } @@ -405,24 +405,23 @@ namespace AppInstaller::Manifest::YamlParser std::vector<ValidationError> ParseManifestImpl( std::vector<YamlManifestInfo>& input, Manifest& manifest, - bool fullValidation, const std::filesystem::path& mergedManifestPath, - bool schemaValidationOnly) + ManifestValidateOption validateOption) { THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 0, "No manifest file found"); - THROW_HR_IF_MSG(E_INVALIDARG, schemaValidationOnly && !mergedManifestPath.empty(), "Manifest cannot be merged if only schema validation is performed"); + THROW_HR_IF_MSG(E_INVALIDARG, validateOption.SchemaValidationOnly && !mergedManifestPath.empty(), "Manifest cannot be merged if only schema validation is performed"); THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 1 && !mergedManifestPath.empty(), "Manifest cannot be merged from a single manifest"); - auto manifestVersion = ValidateInput(input, fullValidation, schemaValidationOnly); + auto manifestVersion = ValidateInput(input, validateOption); std::vector<ValidationError> resultErrors; - if (fullValidation || schemaValidationOnly) + if (validateOption.FullValidation || validateOption.SchemaValidationOnly) { resultErrors = ValidateAgainstSchema(input, manifestVersion); } - if (schemaValidationOnly) + if (validateOption.SchemaValidationOnly) { return resultErrors; } @@ -430,11 +429,11 @@ namespace AppInstaller::Manifest::YamlParser // Merge manifests in multi file manifest case const YAML::Node& manifestDoc = (input.size() > 1) ? MergeMultiFileManifest(input) : input[0].Root; - auto errors = ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, fullValidation); + auto errors = ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, validateOption); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); // Extra semantic validations after basic validation and field population - if (fullValidation) + if (validateOption.FullValidation) { errors = ValidateManifest(manifest); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); @@ -458,10 +457,8 @@ namespace AppInstaller::Manifest::YamlParser Manifest CreateFromPath( const std::filesystem::path& inputPath, - bool fullValidation, - bool throwOnWarning, - const std::filesystem::path& mergedManifestPath, - bool schemaValidationOnly) + ManifestValidateOption validateOption, + const std::filesystem::path& mergedManifestPath) { std::vector<YamlManifestInfo> docList; @@ -492,15 +489,13 @@ namespace AppInstaller::Manifest::YamlParser THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - return ParseManifest(docList, fullValidation, throwOnWarning, mergedManifestPath, schemaValidationOnly); + return ParseManifest(docList, validateOption, mergedManifestPath); } Manifest Create( const std::string& input, - bool fullValidation, - bool throwOnWarning, - const std::filesystem::path& mergedManifestPath, - bool schemaValidationOnly) + ManifestValidateOption validateOption, + const std::filesystem::path& mergedManifestPath) { std::vector<YamlManifestInfo> docList; @@ -515,22 +510,20 @@ namespace AppInstaller::Manifest::YamlParser THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - return ParseManifest(docList, fullValidation, throwOnWarning, mergedManifestPath, schemaValidationOnly); + return ParseManifest(docList, validateOption, mergedManifestPath); } Manifest ParseManifest( std::vector<YamlManifestInfo>& input, - bool fullValidation, - bool throwOnWarning, - const std::filesystem::path& mergedManifestPath, - bool schemaValidationOnly) + ManifestValidateOption validateOption, + const std::filesystem::path& mergedManifestPath) { Manifest manifest; std::vector<ValidationError> errors; try { - errors = ParseManifestImpl(input, manifest, fullValidation, mergedManifestPath, schemaValidationOnly); + errors = ParseManifestImpl(input, manifest, mergedManifestPath, validateOption); } catch (const ManifestException&) { @@ -546,7 +539,7 @@ namespace AppInstaller::Manifest::YamlParser { ManifestException ex{ std::move(errors) }; - if (throwOnWarning || !ex.IsWarningOnly()) + if (validateOption.ThrowOnWarning || !ex.IsWarningOnly()) { THROW_EXCEPTION(ex); } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -27,6 +27,16 @@ namespace AppInstaller::Manifest // The manifest extension for the MS Store constexpr std::string_view s_MSStoreExtension = "msstore"sv; + struct ManifestValidateOption + { + bool SchemaValidationOnly = false; + bool ErrorOnVerifiedPublisherFields = false; + + // Options not exposed in winget util + bool FullValidation = false; + bool ThrowOnWarning = false; + }; + // ManifestVer is inherited from Utility::Version and is a more restricted version. // ManifestVer is used to specify the version of app manifest itself. // ManifestVer is a 3 part version in the format of [0-65535].[0-65535].[0-65535] diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h @@ -42,6 +42,7 @@ namespace AppInstaller::Manifest const char* const InvalidBcp47Value = "The locale value is not a well formed bcp47 language tag."; const char* const BothAllowedAndExcludedMarketsDefined = "Both AllowedMarkets and ExcludedMarkets defined."; const char* const DuplicateReturnCodeEntry = "Duplicate installer return code found."; + const char* const FieldRequireVerifiedPublisher = "Field usage requires verified publishers."; } struct ValidationError diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -24,29 +24,18 @@ namespace AppInstaller::Manifest::YamlParser ManifestTypeEnum ManifestType = ManifestTypeEnum::Preview; }; - // fullValidation: Bool to set if manifest creation should perform extra validation that client does not need. - // e.g. Channel should be null. Client code does not need this check to work properly. - // throwOnWarning: Bool to indicate if an exception should be thrown with only warnings detected in the manifest. - // mergedManifestPath: Output file for merged manifest after processing a multi file manifest - // schemaValidationOnly: Bool to indicate if only schema validation should be performed Manifest CreateFromPath( const std::filesystem::path& inputPath, - bool fullValidation = false, - bool throwOnWarning = false, - const std::filesystem::path& mergedManifestPath = {}, - bool schemaValidationOnly = false); + ManifestValidateOption validateOption = {}, + const std::filesystem::path& mergedManifestPath = {}); Manifest Create( const std::string& input, - bool fullValidation = false, - bool throwOnWarning = false, - const std::filesystem::path& mergedManifestPath = {}, - bool schemaValidationOnly = false); + ManifestValidateOption validateOption = {}, + const std::filesystem::path& mergedManifestPath = {}); Manifest ParseManifest( std::vector<YamlManifestInfo>& input, - bool fullValidation = false, - bool throwOnWarning = false, - const std::filesystem::path& mergedManifestPath = {}, - bool schemaValidationOnly = false); + ManifestValidateOption validateOption = {}, + const std::filesystem::path& mergedManifestPath = {}); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -9,21 +9,26 @@ namespace AppInstaller::Manifest { struct ManifestYamlPopulator { - static std::vector<ValidationError> PopulateManifest(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation); + static std::vector<ValidationError> PopulateManifest( + const YAML::Node& rootNode, + Manifest& manifest, + const ManifestVer& manifestVersion, + ManifestValidateOption validateOption); private: - bool m_fullValidation = false; bool m_isMergedManifest = false; + ManifestValidateOption m_validateOption; // Struct mapping a manifest field to its population logic struct FieldProcessInfo { - FieldProcessInfo(std::string name, std::function<std::vector<ValidationError>(const YAML::Node&)> func) : - Name(std::move(name)), ProcessFunc(func) {} + FieldProcessInfo(std::string name, std::function<std::vector<ValidationError>(const YAML::Node&)> func, bool requireVerifiedPublisher = false) : + Name(std::move(name)), ProcessFunc(func), RequireVerifiedPublisher(requireVerifiedPublisher) {} std::string Name; std::function<std::vector<ValidationError>(const YAML::Node&)> ProcessFunc; + bool RequireVerifiedPublisher = false; }; std::vector<FieldProcessInfo> RootFieldInfos; @@ -79,6 +84,10 @@ namespace AppInstaller::Manifest std::vector<ValidationError> ProcessAppsAndFeaturesEntriesNode(const YAML::Node& appsAndFeaturesEntriesNode); std::vector<ValidationError> ProcessExpectedReturnCodesNode(const YAML::Node& returnCodesNode); - std::vector<ValidationError> PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation); + std::vector<ValidationError> PopulateManifestInternal( + const YAML::Node& rootNode, + Manifest& manifest, + const ManifestVer& manifestVersion, + ManifestValidateOption validateOption); }; } \ No newline at end of file diff --git a/src/WinGetUtil/Exports.cpp b/src/WinGetUtil/Exports.cpp @@ -183,7 +183,12 @@ extern "C" try { - (void)YamlParser::CreateFromPath(manifestPath, true, true); + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + + (void)YamlParser::CreateFromPath(manifestPath, validateOption); + *succeeded = TRUE; } catch (const ManifestException& e) @@ -211,9 +216,14 @@ extern "C" try { - (void)YamlParser::CreateFromPath(inputPath, true, true, - mergedManifestPath ? mergedManifestPath : L"", - option == WinGetValidateManifestOption::SchemaValidationOnly); + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + validateOption.SchemaValidationOnly = WI_IsFlagSet(option, WinGetValidateManifestOption::SchemaValidationOnly); + validateOption.ErrorOnVerifiedPublisherFields = WI_IsFlagSet(option, WinGetValidateManifestOption::ErrorOnVerifiedPublisherFields); + + (void)YamlParser::CreateFromPath(inputPath, validateOption, mergedManifestPath ? mergedManifestPath : L""); + *succeeded = TRUE; } catch (const ManifestException& e) diff --git a/src/WinGetUtil/WinGetUtil.h b/src/WinGetUtil/WinGetUtil.h @@ -19,10 +19,13 @@ extern "C" enum WinGetValidateManifestOption { - Default, - SchemaValidationOnly + Default = 0, + SchemaValidationOnly = 0x1, + ErrorOnVerifiedPublisherFields = 0x2, }; + DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestOption); + // Initializes the logging infrastructure. WINGET_UTIL_API WinGetLoggingInit( WINGET_STRING logPath); diff --git a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp @@ -10,7 +10,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) try { - AppInstaller::Manifest::Manifest manifest = AppInstaller::Manifest::YamlParser::Create(input, false); + AppInstaller::Manifest::Manifest manifest = AppInstaller::Manifest::YamlParser::Create(input); } catch (...) {}