diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
index 4051f4c994..cfa2a0bbe5 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
@@ -707,6 +707,9 @@
true
+
+ true
+
true
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
index f29961ab30..187dafc12a 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
@@ -660,6 +660,9 @@
TestData
+
+ TestData
+
TestData
diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerUrl-BlockScalar.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerUrl-BlockScalar.yaml
new file mode 100644
index 0000000000..18d2ed7620
--- /dev/null
+++ b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerUrl-BlockScalar.yaml
@@ -0,0 +1,17 @@
+# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json
+
+PackageIdentifier: microsoft.msixsdk
+PackageName: MSIX SDK
+PackageVersion: 1.07.32-beta
+Publisher: Microsoft
+ShortDescription: description
+License: Test
+PackageLocale: en-US
+Installers:
+ - Architecture: x86
+ InstallerUrl: >
+ https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.msi
+ InstallerSha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD
+ InstallerType: msi
+ManifestType: singleton
+ManifestVersion: 1.1.0
diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp
index f893881f83..f78a5fdcb1 100644
--- a/src/AppInstallerCLITests/YamlManifest.cpp
+++ b/src/AppInstallerCLITests/YamlManifest.cpp
@@ -958,6 +958,15 @@ TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]")
REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" });
}
+TEST_CASE("ReadGoodManifestInstallerUrlBlockScalar", "[ManifestValidation]")
+{
+ ManifestValidateOption validateOption = GetTestManifestValidateOption();
+ Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-InstallerUrl-BlockScalar.yaml"), validateOption);
+
+ REQUIRE(manifest.Installers.size() == 1);
+ REQUIRE(manifest.Installers[0].Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.msi");
+}
+
TEST_CASE("ReadGoodManifests", "[ManifestValidation]")
{
ManifestTestCase TestCases[] =
@@ -973,6 +982,7 @@ TEST_CASE("ReadGoodManifests", "[ManifestValidation]")
{ "Manifest-Good-Minimum-InstallerType.yaml" },
{ "Manifest-Good-Switches.yaml" },
{ "Manifest-Good-DefaultExpectedReturnCodeInInstallerSuccessCodes.yaml" },
+ { "Manifest-Good-InstallerUrl-BlockScalar.yaml" },
{ "Manifest-Good-InstallerTypeZip-PortableExe.yaml" },
{ "Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml" },
};
diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp
index b58c2e7a32..fff3806f44 100644
--- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp
+++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp
@@ -47,6 +47,19 @@ namespace AppInstaller::Manifest::YamlParser
return YamlScalarType::String;
}
+ // YAML block scalars with clip/keep chomping retain a trailing line break.
+ // Schema patterns anchored with `$` fail against that break under current
+ // MSVC std::regex ECMAScript semantics (LWG-2503), so strip it before validation.
+ std::string TrimTrailingLineBreaks(std::string value)
+ {
+ while (!value.empty() && (value.back() == '\n' || value.back() == '\r'))
+ {
+ value.pop_back();
+ }
+
+ return value;
+ }
+
Json::Value YamlScalarNodeToJson(const YAML::Node& scalarNode, YamlScalarType scalarType)
{
if (scalarType == YamlScalarType::Int)
@@ -59,7 +72,7 @@ namespace AppInstaller::Manifest::YamlParser
}
else
{
- return Json::Value(scalarNode.as());
+ return Json::Value(TrimTrailingLineBreaks(scalarNode.as()));
}
}
diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp
index ad02015068..60df8f56f4 100644
--- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp
+++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp
@@ -247,7 +247,7 @@ namespace AppInstaller::Manifest
std::vector installerOnlyFields =
{
{ "Arch", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } },
- { "Url", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = value.as(); return {}; } },
+ { "Url", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = Utility::Trim(value.as()); return {}; } },
{ "Sha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } },
{ "SignatureSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } },
{ "Language", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Locale = value.as(); return {}; } },
@@ -307,7 +307,7 @@ namespace AppInstaller::Manifest
std::vector v1InstallerFields =
{
{ "Architecture", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } },
- { "InstallerUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = value.as(); return {}; } },
+ { "InstallerUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = Utility::Trim(value.as()); return {}; } },
{ "InstallerSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } },
{ "SignatureSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } },
// No custom validation needed at field populating time since we have semantic validation later to block msstore and productId from community repo.