winget-cli

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

ManifestValidation.cpp (29819B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "AppInstallerLogging.h"
      5 #include "AppInstallerMsixInfo.h"
      6 #include "winget/MsixManifest.h"
      7 #include "winget/ManifestValidation.h"
      8 #include "winget/MsixManifestValidation.h"
      9 #include "winget/Locale.h"
     10 #include "winget/Filesystem.h"
     11 
     12 namespace AppInstaller::Manifest
     13 {
     14     namespace
     15     {
     16         constexpr std::array<std::wstring_view, 1> s_AllowedPortableFiletypes = {
     17             L".exe",
     18         };
     19 
     20         constexpr std::array<std::wstring_view, 5> s_AllowedFontFiletypes = {
     21             L".otf",         // OpenType Font
     22             L".ttf",         // TrueType Font
     23             L".fnt",         // Font
     24             L".ttc",         // TrueType Font Collection
     25             L".otc",         // OpenType Font Collection
     26         };
     27 
     28         const auto& GetErrorIdToMessageMap()
     29         {
     30             static std::map<AppInstaller::StringResource::StringId, std::string_view> ErrorIdToMessageMap = {
     31                 { AppInstaller::Manifest::ManifestError::InvalidRootNode, "Encountered unexpected root node."sv },
     32                 { AppInstaller::Manifest::ManifestError::FieldUnknown, "Unknown field."sv },
     33                 { AppInstaller::Manifest::ManifestError::FieldIsNotPascalCase, "All field names should be PascalCased."sv },
     34                 { AppInstaller::Manifest::ManifestError::FieldDuplicate, "Duplicate field found in the manifest."sv },
     35                 { AppInstaller::Manifest::ManifestError::RequiredFieldEmpty, "Required field with empty value."sv },
     36                 { AppInstaller::Manifest::ManifestError::RequiredFieldMissing,  "Required field missing."sv },
     37                 { AppInstaller::Manifest::ManifestError::InvalidFieldValue, "Invalid field value."sv },
     38                 { AppInstaller::Manifest::ManifestError::ExeInstallerMissingSilentSwitches, "Silent and SilentWithProgress switches are not specified for InstallerType exe. Please make sure the installer can run unattended."sv },
     39                 { AppInstaller::Manifest::ManifestError::FieldNotSupported, "Field is not supported."sv },
     40                 { AppInstaller::Manifest::ManifestError::FieldValueNotSupported, "Field value is not supported."sv },
     41                 { AppInstaller::Manifest::ManifestError::DuplicateInstallerEntry, "Duplicate installer entry found."sv },
     42                 { AppInstaller::Manifest::ManifestError::DuplicateInstallerHash, "Multiple Installer URLs found with the same InstallerSha256. Please ensure the accuracy of the URLs."sv },
     43                 { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "The specified installer type does not support PackageFamilyName."sv },
     44                 { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportProductCode, "The specified installer type does not support ProductCode."sv },
     45                 { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "The specified installer type does not write to Apps and Features entry."sv },
     46                 { AppInstaller::Manifest::ManifestError::IncompleteMultiFileManifest, "The multi file manifest is incomplete.A multi file manifest must contain at least version, installer and defaultLocale manifest."sv },
     47                 { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestFieldValue, "The multi file manifest has inconsistent field values."sv },
     48                 { AppInstaller::Manifest::ManifestError::DuplicatePortableCommandAlias, "Duplicate portable command alias found."sv },
     49                 { AppInstaller::Manifest::ManifestError::DuplicateRelativeFilePath, "Duplicate relative file path found."sv },
     50                 { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestType, "The multi file manifest should contain only one file with the particular ManifestType."sv },
     51                 { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestLocale, "The multi file manifest contains duplicate PackageLocale."sv },
     52                 { AppInstaller::Manifest::ManifestError::UnsupportedMultiFileManifestType, "The multi file manifest should not contain file with the particular ManifestType."sv },
     53                 { AppInstaller::Manifest::ManifestError::InconsistentInstallerHash, "The values of InstallerSha256 do not match for all instances of the same InstallerUrl."sv },
     54                 { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestDefaultLocale, "DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest."sv },
     55                 { AppInstaller::Manifest::ManifestError::FieldFailedToProcess, "Failed to process field."sv },
     56                 { AppInstaller::Manifest::ManifestError::InvalidBcp47Value, "The locale value is not a well formed bcp47 language tag."sv },
     57                 { AppInstaller::Manifest::ManifestError::BothAllowedAndExcludedMarketsDefined, "Both AllowedMarkets and ExcludedMarkets defined."sv },
     58                 { AppInstaller::Manifest::ManifestError::DuplicateReturnCodeEntry, "Duplicate installer return code found."sv },
     59                 { AppInstaller::Manifest::ManifestError::FieldRequireVerifiedPublisher, "Field usage requires verified publishers."sv },
     60                 { AppInstaller::Manifest::ManifestError::SingleManifestPackageHasDependencies, "Package has a single manifest and is a dependency of other manifests."sv },
     61                 { AppInstaller::Manifest::ManifestError::MultiManifestPackageHasDependencies, "Deleting the manifest will be break the following dependencies."sv },
     62                 { AppInstaller::Manifest::ManifestError::MissingManifestDependenciesNode, "Dependency not found: "sv },
     63                 { AppInstaller::Manifest::ManifestError::NoSuitableMinVersionDependency,"No Suitable Minimum Version: "sv },
     64                 { AppInstaller::Manifest::ManifestError::FoundDependencyLoop, "Loop found."sv },
     65                 { AppInstaller::Manifest::ManifestError::ExceededAppsAndFeaturesEntryLimit, "Only zero or one entry for Apps and Features may be specified for InstallerType portable."sv },
     66                 { AppInstaller::Manifest::ManifestError::ExceededCommandsLimit, "Only zero or one value for Commands may be specified for InstallerType portable."sv },
     67                 { AppInstaller::Manifest::ManifestError::ScopeNotSupported, "Scope is not supported for InstallerType portable."sv },
     68                 { AppInstaller::Manifest::ManifestError::InstallerMsixInconsistencies, "Inconsistent value in the manifest."sv },
     69                 { AppInstaller::Manifest::ManifestError::OptionalFieldMissing, "Optional field missing."sv },
     70                 { AppInstaller::Manifest::ManifestError::InstallerFailedToProcess, "Failed to process installer."sv },
     71                 { AppInstaller::Manifest::ManifestError::NoSupportedPlatforms, "No supported platforms."sv },
     72                 { AppInstaller::Manifest::ManifestError::ApproximateVersionNotAllowed, "Approximate version not allowed."sv },
     73                 { AppInstaller::Manifest::ManifestError::ArpVersionOverlapWithIndex, "DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index. Existing DisplayVersion range in index: "sv },
     74                 { AppInstaller::Manifest::ManifestError::ArpVersionValidationInternalError, "Internal error while validating DisplayVersion against index."sv },
     75                 { AppInstaller::Manifest::ManifestError::ExceededNestedInstallerFilesLimit, "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes."sv },
     76                 { AppInstaller::Manifest::ManifestError::RelativeFilePathEscapesDirectory, "Relative file path must not point to a location outside of archive directory."sv },
     77                 { AppInstaller::Manifest::ManifestError::ArpValidationError, "Arp Validation Error."sv },
     78                 { AppInstaller::Manifest::ManifestError::SchemaError, "Schema Error."sv },
     79                 { AppInstaller::Manifest::ManifestError::MsixSignatureHashFailed, "Failed to calculate MSIX signature hash.Please verify that the input file is a valid, signed MSIX."sv },
     80                 { AppInstaller::Manifest::ManifestError::ShadowManifestNotAllowed, "Shadow manifest is not allowed." },
     81                 { AppInstaller::Manifest::ManifestError::SchemaHeaderNotFound, "Schema header not found." },
     82                 { AppInstaller::Manifest::ManifestError::InvalidSchemaHeader , "The schema header is invalid. Please verify that the schema header is present and formatted correctly."sv },
     83                 { AppInstaller::Manifest::ManifestError::SchemaHeaderManifestTypeMismatch , "The manifest type in the schema header does not match the ManifestType property value in the manifest."sv },
     84                 { AppInstaller::Manifest::ManifestError::SchemaHeaderManifestVersionMismatch, "The manifest version in the schema header does not match the ManifestVersion property value in the manifest."sv },
     85                 { AppInstaller::Manifest::ManifestError::SchemaHeaderUrlPatternMismatch, "The schema header URL does not match the expected pattern."sv },
     86                 { AppInstaller::Manifest::ManifestError::InvalidPortableFiletype, "The file type of the referenced file is not allowed."sv },
     87                 { AppInstaller::Manifest::ManifestError::InvalidFontFiletype, "The file type of the referenced file is not a supported font file type."sv },
     88             };
     89 
     90             return ErrorIdToMessageMap;
     91         }
     92     }
     93     std::vector<ValidationError> ValidateManifest(const Manifest& manifest, bool fullValidation)
     94     {
     95         std::vector<ValidationError> resultErrors;
     96 
     97         // Channel is not supported currently
     98         if (!manifest.Channel.empty())
     99         {
    100             resultErrors.emplace_back(ManifestError::FieldNotSupported, "Channel", manifest.Channel);
    101         }
    102 
    103         try
    104         {
    105             // Version value should be successfully parsed
    106             Utility::Version testVersion{ manifest.Version };
    107             if (testVersion.IsApproximate())
    108             {
    109                 resultErrors.emplace_back(ManifestError::ApproximateVersionNotAllowed, "PackageVersion", manifest.Version);
    110             }
    111         }
    112         catch (const std::exception&)
    113         {
    114             resultErrors.emplace_back(ManifestError::InvalidFieldValue, "PackageVersion", manifest.Version);
    115         }
    116 
    117         auto defaultLocErrors = ValidateManifestLocalization(manifest.DefaultLocalization, !fullValidation);
    118         std::move(defaultLocErrors.begin(), defaultLocErrors.end(), std::inserter(resultErrors, resultErrors.end()));
    119 
    120         // Comparison function to check duplicate installer entry. {installerType, arch, language and scope} combination is the key.
    121         // Todo: use the comparator from ManifestComparator when that one is fully implemented.
    122         auto installerCmp = [](const ManifestInstaller& in1, const ManifestInstaller& in2)
    123         {
    124             if (in1.BaseInstallerType != in2.BaseInstallerType)
    125             {
    126                 return in1.BaseInstallerType < in2.BaseInstallerType;
    127             }
    128             else if (IsArchiveType(in1.BaseInstallerType))
    129             {
    130                 // Compare nested installer type if base installer type is archive.
    131                 if (in1.NestedInstallerType != in2.NestedInstallerType)
    132                 {
    133                     return in1.NestedInstallerType < in2.NestedInstallerType;
    134                 }
    135             }
    136 
    137             if (in1.Arch != in2.Arch)
    138             {
    139                 return in1.Arch < in2.Arch;
    140             }
    141 
    142             if (in1.Locale != in2.Locale)
    143             {
    144                 return in1.Locale < in2.Locale;
    145             }
    146 
    147             // Unknown is considered equal to all other values for uniqueness.
    148             // If either value is unknown, don't compare them.
    149             if (in1.Scope != in2.Scope && in1.Scope != ScopeEnum::Unknown && in2.Scope != ScopeEnum::Unknown)
    150             {
    151                 return in1.Scope < in2.Scope;
    152             }
    153 
    154             return false;
    155         };
    156 
    157         std::set<ManifestInstaller, decltype(installerCmp)> installerSet(installerCmp);
    158         bool duplicateInstallerFound = false;
    159 
    160         // Set up maps for checking uniqueness across hash <-> url pairs
    161         std::unordered_map<std::string, std::string> urlToChecksum;
    162         std::unordered_map<std::string, std::string> checksumToUrl;
    163 
    164         // Validate installers
    165         for (auto const& installer : manifest.Installers)
    166         {
    167             // If not full validation, for future compatibility, skip validating unknown installers.
    168             if (installer.EffectiveInstallerType() == InstallerTypeEnum::Unknown && !fullValidation)
    169             {
    170                 continue;
    171             }
    172 
    173             if (!duplicateInstallerFound && !installerSet.insert(installer).second)
    174             {
    175                 AICLI_LOG(Core, Error, << "Duplicate installer: Type [" << InstallerTypeToString(installer.EffectiveInstallerType()) <<
    176                     "], Architecture [" << Utility::ToString(installer.Arch) << "], Locale [" << installer.Locale <<
    177                     "], Scope [" << ScopeToString(installer.Scope) << "]");
    178 
    179                 resultErrors.emplace_back(ManifestError::DuplicateInstallerEntry);
    180                 duplicateInstallerFound = true;
    181             }
    182 
    183             if (installer.Arch == Utility::Architecture::Unknown)
    184             {
    185                 resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Architecture");
    186             }
    187 
    188             if (installer.EffectiveInstallerType() == InstallerTypeEnum::Unknown)
    189             {
    190                 resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerType");
    191             }
    192 
    193             if (installer.UpdateBehavior == UpdateBehaviorEnum::Unknown)
    194             {
    195                 resultErrors.emplace_back(ManifestError::InvalidFieldValue, "UpgradeBehavior");
    196             }
    197 
    198             // Validate system reference strings if they are set at the installer level
    199             // Allow PackageFamilyName to be declared with non msix installers to support nested installer scenarios. But still report as warning to notify user of this uncommon case.
    200             if (!installer.PackageFamilyName.empty() && !DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()))
    201             {
    202                 resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "InstallerType", std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }, ValidationError::Level::Warning);
    203             }
    204 
    205             if (!installer.ProductCode.empty() && !DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType()))
    206             {
    207                 resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportProductCode, "InstallerType", InstallerTypeToString(installer.EffectiveInstallerType()));
    208             }
    209 
    210             if (!installer.AppsAndFeaturesEntries.empty() && !DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType()))
    211             {
    212                 resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "InstallerType", InstallerTypeToString(installer.EffectiveInstallerType()));
    213             }
    214 
    215             if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore)
    216             {
    217                 if (fullValidation)
    218                 {
    219                     // MSStore type is not supported in community repo
    220                     resultErrors.emplace_back(
    221                         ManifestError::FieldValueNotSupported, "InstallerType",
    222                         InstallerTypeToString(installer.EffectiveInstallerType()));
    223                 }
    224 
    225                 if (installer.ProductId.empty())
    226                 {
    227                     resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "ProductId");
    228                 }
    229             }
    230             else
    231             {
    232                 // For other types, Url and Sha256 are required
    233                 if (installer.Url.empty())
    234                 {
    235                     resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "InstallerUrl");
    236                 }
    237                 if (installer.Sha256.empty())
    238                 {
    239                     resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "InstallerSha256");
    240                 }
    241                 // ProductId should not be used
    242                 if (!installer.ProductId.empty())
    243                 {
    244                     resultErrors.emplace_back(ManifestError::FieldNotSupported, "ProductId");
    245                 }
    246 
    247                 // Ensure that each URL has a one to one mapping with a Sha256 and
    248                 // warn if a Sha256 has a one to many mapping with a URL
    249                 if (fullValidation && !installer.Url.empty() && !installer.Sha256.empty())
    250                 {
    251                     std::string checksum = Utility::SHA256::ConvertToString(installer.Sha256);
    252                     std::string url = installer.Url;
    253 
    254                     auto [urlIterator, urlInserted] = urlToChecksum.try_emplace(url, checksum);
    255                     auto [checksumIterator, checksumInserted] = checksumToUrl.try_emplace(checksum, url);
    256 
    257                     if (!urlInserted && urlIterator->second != checksum)
    258                     {
    259                         // If the URL was not inserted, and the value in the map does not match the current Sha256, then
    260                         // a single URL corresponds to multiple SHA256 and an error should be thrown
    261                         resultErrors.emplace_back(ManifestError::InconsistentInstallerHash, "InstallerUrl", url);
    262                     }
    263 
    264                     if (!checksumInserted && checksumIterator->second != url)
    265                     {
    266                         // If the SHA256 was not inserted, and the value in the map does not match the current URL, then
    267                         // a single SHA256 corresponds to multiple URLS and a warning should be thrown
    268                         resultErrors.emplace_back(ManifestError::DuplicateInstallerHash, "InstallerSha256", checksum, ValidationError::Level::Warning);
    269                     }
    270                 }
    271             }
    272 
    273             if (installer.EffectiveInstallerType() == InstallerTypeEnum::Exe &&
    274                 (installer.Switches.find(InstallerSwitchType::SilentWithProgress) == installer.Switches.end() ||
    275                  installer.Switches.find(InstallerSwitchType::Silent) == installer.Switches.end()))
    276             {
    277                 resultErrors.emplace_back(ManifestError::ExeInstallerMissingSilentSwitches, ValidationError::Level::Warning);
    278             }
    279 
    280             // The command field restriction only applies if the base installer type is Portable.
    281             if (installer.BaseInstallerType == InstallerTypeEnum::Portable)
    282             {
    283                 if (installer.Commands.size() > 1)
    284                 {
    285                     resultErrors.emplace_back(ManifestError::ExceededCommandsLimit);
    286                 }
    287             }
    288 
    289             if (installer.EffectiveInstallerType() == InstallerTypeEnum::Portable)
    290             {
    291                 if (installer.AppsAndFeaturesEntries.size() > 1)
    292                 {
    293                     resultErrors.emplace_back(ManifestError::ExceededAppsAndFeaturesEntryLimit);
    294                 }
    295                 if (installer.Scope != ScopeEnum::Unknown)
    296                 {
    297                     resultErrors.emplace_back(ManifestError::ScopeNotSupported, ValidationError::Level::Warning);
    298                 }
    299             }
    300 
    301             if (IsArchiveType(installer.BaseInstallerType))
    302             {
    303                 bool isPortable = installer.NestedInstallerType == InstallerTypeEnum::Portable;
    304                 bool isFont = installer.NestedInstallerType == InstallerTypeEnum::Font;
    305 
    306                 if (installer.NestedInstallerType == InstallerTypeEnum::Unknown)
    307                 {
    308                     resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "NestedInstallerType");
    309                 }
    310                 if (installer.NestedInstallerFiles.size() == 0)
    311                 {
    312                     resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "NestedInstallerFiles");
    313                 }
    314                 if (!isPortable && !isFont && installer.NestedInstallerFiles.size() != 1)
    315                 {
    316                     resultErrors.emplace_back(ManifestError::ExceededNestedInstallerFilesLimit, "NestedInstallerFiles");
    317                 }
    318 
    319                 std::set<std::string> commandAliasSet;
    320                 std::set<std::string> relativeFilePathSet;
    321 
    322                 for (const auto& nestedInstallerFile : installer.NestedInstallerFiles)
    323                 {
    324                     if (nestedInstallerFile.RelativeFilePath.empty())
    325                     {
    326                         resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "RelativeFilePath");
    327                         break;
    328                     }
    329 
    330                     // Check that the relative file path does not escape base directory.
    331                     const std::filesystem::path& basePath = std::filesystem::current_path();
    332                     const std::filesystem::path& fullPath = basePath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath);
    333                     if (AppInstaller::Filesystem::PathEscapesBaseDirectory(fullPath, basePath))
    334                     {
    335                         resultErrors.emplace_back(ManifestError::RelativeFilePathEscapesDirectory, "RelativeFilePath");
    336                     }
    337 
    338                     // Check for duplicate relative filepath values.
    339                     if (!relativeFilePathSet.insert(Utility::ToLower(nestedInstallerFile.RelativeFilePath)).second)
    340                     {
    341                         resultErrors.emplace_back(ManifestError::DuplicateRelativeFilePath, "RelativeFilePath");
    342                     }
    343 
    344                     // Check for duplicate portable command alias values.
    345                     const auto& alias = Utility::ToLower(nestedInstallerFile.PortableCommandAlias);
    346                     if (!alias.empty() && !commandAliasSet.insert(alias).second)
    347                     {
    348                         resultErrors.emplace_back(ManifestError::DuplicatePortableCommandAlias, "PortableCommandAlias");
    349                         break;
    350                     }
    351 
    352                     // If running full validation, check filetype
    353                     if (fullValidation)
    354                     {
    355                         if (isPortable)
    356                         {
    357                             if (fullPath.has_extension() && std::find(s_AllowedPortableFiletypes.begin(), s_AllowedPortableFiletypes.end(), fullPath.extension()) == s_AllowedPortableFiletypes.end())
    358                             {
    359                                 resultErrors.emplace_back(ManifestError::InvalidPortableFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath);
    360                             }
    361                         }
    362 
    363                         if (isFont)
    364                         {
    365                             if (fullPath.has_extension() && std::find(s_AllowedFontFiletypes.begin(), s_AllowedFontFiletypes.end(), fullPath.extension()) == s_AllowedFontFiletypes.end())
    366                             {
    367                                 resultErrors.emplace_back(ManifestError::InvalidFontFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath);
    368                             }
    369                         }
    370                     }
    371                 }
    372             }
    373 
    374             // Check empty string before calling IsValidUrl to avoid duplicate error reporting.
    375             if (!installer.Url.empty() && IsValidURL(NULL, Utility::ConvertToUTF16(installer.Url).c_str(), 0) == S_FALSE)
    376             {
    377                 resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerUrl", installer.Url);
    378             }
    379 
    380             if (!installer.Locale.empty() && !Locale::IsWellFormedBcp47Tag(installer.Locale))
    381             {
    382                 resultErrors.emplace_back(ManifestError::InvalidBcp47Value, "InstallerLocale", installer.Locale);
    383             }
    384 
    385             if (!installer.Markets.AllowedMarkets.empty() && !installer.Markets.ExcludedMarkets.empty())
    386             {
    387                 resultErrors.emplace_back(ManifestError::BothAllowedAndExcludedMarketsDefined);
    388             }
    389 
    390             // Check expected return codes for duplicates between successful and expected error codes
    391             std::set<DWORD> returnCodeSet;
    392             returnCodeSet.insert(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end());
    393             for (const auto& code : installer.ExpectedReturnCodes)
    394             {
    395                 if (!returnCodeSet.insert(code.first).second)
    396                 {
    397                     resultErrors.emplace_back(ManifestError::DuplicateReturnCodeEntry);
    398 
    399                     // Stop checking to avoid repeated errors
    400                     break;
    401                 }
    402             }
    403 
    404             // Check no approximate version declared for DisplayVersion in AppsAndFeatureEntries
    405             for (auto const& entry : installer.AppsAndFeaturesEntries)
    406             {
    407                 if (!entry.DisplayVersion.empty())
    408                 {
    409                     try
    410                     {
    411                         Utility::Version displayVersion{ entry.DisplayVersion };
    412                         if (displayVersion.IsApproximate())
    413                         {
    414                             resultErrors.emplace_back(ManifestError::ApproximateVersionNotAllowed, "DisplayVersion", entry.DisplayVersion);
    415                         }
    416                     }
    417                     catch (const std::exception&)
    418                     {
    419                         resultErrors.emplace_back(ManifestError::InvalidFieldValue, "DisplayVersion", entry.DisplayVersion);
    420                     }
    421                 }
    422             }
    423 
    424             // Check AuthInfo validity. For full validation (community repo), authentication type must be none.
    425             if (installer.AuthInfo.Type != Authentication::AuthenticationType::None)
    426             {
    427                 if (fullValidation)
    428                 {
    429                     // Authentication is not supported (must be none) in community repo.
    430                     resultErrors.emplace_back(ManifestError::FieldNotSupported, "Authentication");
    431                 }
    432 
    433                 if (!installer.AuthInfo.ValidateIntegrity())
    434                 {
    435                     resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Authentication");
    436                 }
    437             }
    438         }
    439 
    440         // Validate localizations
    441         for (auto const& localization : manifest.Localizations)
    442         {
    443             auto locErrors = ValidateManifestLocalization(localization, !fullValidation);
    444             std::move(locErrors.begin(), locErrors.end(), std::inserter(resultErrors, resultErrors.end()));
    445         }
    446 
    447         return resultErrors;
    448     }
    449 
    450     std::vector<ValidationError> ValidateManifestLocalization(const ManifestLocalization& localization, bool treatErrorAsWarning)
    451     {
    452         std::vector<ValidationError> resultErrors;
    453 
    454         if (!localization.Locale.empty() && !Locale::IsWellFormedBcp47Tag(localization.Locale))
    455         {
    456             resultErrors.emplace_back(ManifestError::InvalidBcp47Value, "PackageLocale", localization.Locale, treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error);
    457         }
    458 
    459         if (localization.Contains(Localization::Agreements))
    460         {
    461             const auto& agreements = localization.Get<Localization::Agreements>();
    462             for (const auto& agreement : agreements)
    463             {
    464                 // At least one must be present
    465                 if (agreement.Label.empty() && agreement.AgreementText.empty() && agreement.AgreementUrl.empty())
    466                 {
    467                     resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Agreements", treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error);
    468                 }
    469             }
    470         }
    471 
    472         return resultErrors;
    473     }
    474 
    475     std::vector<ValidationError> ValidateManifestInstallers(const Manifest& manifest, bool treatErrorAsWarning)
    476     {
    477         std::vector<ValidationError> errors;
    478         auto validationErrorLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error;
    479         MsixManifestValidation msixManifestValidation(validationErrorLevel);
    480         for (const auto& installer : manifest.Installers)
    481         {
    482             // Installer msix or msixbundle
    483             if (installer.EffectiveInstallerType() == InstallerTypeEnum::Msix)
    484             {
    485                 auto installerErrors = msixManifestValidation.Validate(manifest, installer);
    486                 std::move(installerErrors.begin(), installerErrors.end(), std::inserter(errors, errors.end()));
    487             }
    488         }
    489 
    490         return errors;
    491     }
    492 
    493     std::string ValidationError::GetErrorMessage() const
    494     {
    495         const auto& ErrorIdToMessageMap = GetErrorIdToMessageMap();
    496         const auto itr = ErrorIdToMessageMap.find(Message);
    497 
    498         if (itr != ErrorIdToMessageMap.end())
    499         {
    500             return std::string(itr->second);
    501         }
    502         
    503         return Utility::ConvertToUTF8(Message);
    504     }
    505 }