diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index c2d02de16e..142236ace8 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -59,3 +59,4 @@ The PowerShell module now automatically uses `GH_TOKEN` or `GITHUB_TOKEN` enviro * `SignFile` in `WinGetSourceCreator` now supports an optional RFC 3161 timestamp server via the new `TimestampServer` property on the `Signature` model. When set, `signtool.exe` is called with `/tr /td sha256`, embedding a countersignature timestamp so that signed packages remain valid after the signing certificate expires. * File and directory paths passed to `signtool.exe` and `makeappx.exe` are now quoted, fixing failures when paths contain spaces. * DSC export now correctly exports WinGet Admin Settings +* `winget validate` now performs case-insensitive comparison for file extensions where applicable diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 5cd2f1b486..f796e3d925 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -1048,6 +1048,9 @@ true + + true + true diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 1252862fa4..5ac6008d1d 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -1119,6 +1119,9 @@ TestData + + TestData + TestData diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml new file mode 100644 index 0000000000..af5a25e77f --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml @@ -0,0 +1,21 @@ +# Good manifest. Installer type zip with NestedInstallerType portable should allow uppercase .EXE files +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.9.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 uppercase .EXE extension +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + NestedInstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerFiles: + - RelativeFilePath: GoodApplication.EXE +ManifestType: singleton +ManifestVersion: 1.9.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index dda1c1c9e4..420dcd9d0d 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -842,6 +842,7 @@ TEST_CASE("ReadGoodManifests", "[ManifestValidation]") { "Manifest-Good-Switches.yaml" }, { "Manifest-Good-DefaultExpectedReturnCodeInInstallerSuccessCodes.yaml" }, { "Manifest-Good-InstallerTypeZip-PortableExe.yaml" }, + { "Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml" }, }; for (auto const& testCase : TestCases) @@ -1348,6 +1349,7 @@ TEST_CASE("PortableFileTypeValidation", "[ManifestValidation]") { Manifest installerManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InstallerTypeZip-PortableNotExe.yaml")); Manifest rootManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InstallerTypeZip-PortableNotExe_Root.yaml")); + Manifest uppercaseManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml")); // Regular validation should detect as error auto errors = ValidateManifest(installerManifest, true); @@ -1364,6 +1366,10 @@ TEST_CASE("PortableFileTypeValidation", "[ManifestValidation]") errors = ValidateManifest(rootManifest, false); REQUIRE(errors.size() == 0); + + // Uppercase file extension should be accepted (case-insensitive comparison) + errors = ValidateManifest(uppercaseManifest, true); + REQUIRE(errors.size() == 0); } TEST_CASE("WindowsFeatureNameValidation", "[ManifestValidation][111981]") diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index 2f877ceb60..fded41c56e 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -370,9 +370,11 @@ namespace AppInstaller::Manifest // If running full validation, check filetype if (options.FullValidation) { + const std::wstring lowerExtension = Utility::ToLower(fullPath.extension().wstring()); + if (isPortable) { - if (fullPath.has_extension() && std::find(s_AllowedPortableFiletypes.begin(), s_AllowedPortableFiletypes.end(), fullPath.extension()) == s_AllowedPortableFiletypes.end()) + if (fullPath.has_extension() && std::find(s_AllowedPortableFiletypes.begin(), s_AllowedPortableFiletypes.end(), lowerExtension) == s_AllowedPortableFiletypes.end()) { resultErrors.emplace_back(ManifestError::InvalidPortableFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); } @@ -380,7 +382,7 @@ namespace AppInstaller::Manifest if (isFont) { - if (fullPath.has_extension() && std::find(s_AllowedFontFiletypes.begin(), s_AllowedFontFiletypes.end(), fullPath.extension()) == s_AllowedFontFiletypes.end()) + if (fullPath.has_extension() && std::find(s_AllowedFontFiletypes.begin(), s_AllowedFontFiletypes.end(), lowerExtension) == s_AllowedFontFiletypes.end()) { resultErrors.emplace_back(ManifestError::InvalidFontFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); }