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 }