UserSettings.cpp (23471B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "AppInstallerRuntime.h" 5 #include "AppInstallerLanguageUtilities.h" 6 #include "AppInstallerLogging.h" 7 #include "winget/JsonUtil.h" 8 #include "winget/Settings.h" 9 #include "winget/UserSettings.h" 10 #include "winget/filesystem.h" 11 12 #include "AppInstallerArchitecture.h" 13 #include "winget/Locale.h" 14 15 namespace AppInstaller::Settings 16 { 17 using namespace std::string_view_literals; 18 using namespace Runtime; 19 using namespace Utility; 20 using namespace Logging; 21 using namespace JSON; 22 using namespace Filesystem; 23 24 static constexpr std::string_view s_SettingEmpty = 25 R"({ 26 "$schema": "https://aka.ms/winget-settings.schema.json", 27 28 // For documentation on these settings, see: https://aka.ms/winget-settings 29 // "source": { 30 // "autoUpdateIntervalInMinutes": 5 31 // }, 32 })"sv; 33 34 namespace 35 { 36 template<class T> 37 inline std::string GetValueString(T value) 38 { 39 std::string convertedValue; 40 41 if constexpr (std::is_arithmetic_v<T>) 42 { 43 convertedValue = std::to_string(value); 44 } 45 else 46 { 47 convertedValue = value; 48 } 49 50 return convertedValue; 51 } 52 53 template<> 54 inline std::string GetValueString(std::vector<std::string> value) 55 { 56 std::string convertedValue = "["; 57 58 bool first = true; 59 for (auto const& entry : value) 60 { 61 if (first) 62 { 63 first = false; 64 } 65 else 66 { 67 convertedValue += ", "; 68 } 69 70 convertedValue += entry; 71 } 72 73 convertedValue += ']'; 74 75 return convertedValue; 76 } 77 78 std::optional<Json::Value> ParseSettingsContent(const std::string& content, std::string_view settingName, std::vector<UserSettings::Warning>& warnings) 79 { 80 Json::Value root; 81 Json::CharReaderBuilder builder; 82 const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); 83 std::string error; 84 85 if (reader->parse(content.c_str(), content.c_str() + content.size(), &root, &error)) 86 { 87 return root; 88 } 89 90 AICLI_LOG(Core, Error, << "Error parsing " << settingName << ": " << error); 91 warnings.emplace_back(StringResource::String::SettingsWarningParseError, settingName, error, false); 92 93 return {}; 94 } 95 96 std::optional<Json::Value> ParseFile(const StreamDefinition& setting, std::vector<UserSettings::Warning>& warnings) 97 { 98 try 99 { 100 auto stream = Stream{ setting }.Get(); 101 if (stream) 102 { 103 std::string settingsContentStr = Utility::ReadEntireStream(*stream); 104 return ParseSettingsContent(settingsContentStr, setting.Name, warnings); 105 } 106 } 107 catch (const std::exception& e) 108 { 109 AICLI_LOG(Core, Error, << "Failed to read " << setting.Name << "; Reason: " << e.what()); 110 } 111 catch (...) 112 { 113 AICLI_LOG(Core, Error, << "Failed to read " << setting.Name << "; Reason unknown."); 114 } 115 116 return {}; 117 } 118 119 template <Setting S> 120 std::optional<typename details::SettingMapping<S>::json_t> GetValueFromPolicy() 121 { 122 return GroupPolicies().GetValue<details::SettingMapping<S>::Policy>(); 123 } 124 125 template <Setting S> 126 void Validate( 127 Json::Value& root, 128 std::map<Setting, details::SettingVariant>& settings, 129 std::vector<UserSettings::Warning>& warnings) 130 { 131 // jsoncpp doesn't support std::string_view yet. 132 auto path = std::string(details::SettingMapping<S>::Path); 133 134 // Settings set by Group Policy override anything else. See if there is one. 135 auto policyValue = GetValueFromPolicy<S>(); 136 if (policyValue.has_value()) 137 { 138 // If the value is valid, use it. 139 // Otherwise, fall back to default. 140 // In any case, we do not need to read the setting from the JSON. 141 auto validatedValue = details::SettingMapping<S>::Validate(policyValue.value()); 142 if (validatedValue.has_value()) 143 { 144 // Add it to the map 145 settings[S].emplace<details::SettingIndex(S)>( 146 std::forward<typename details::SettingMapping<S>::value_t>(validatedValue.value())); 147 AICLI_LOG(Core, Verbose, << "Valid setting from Group Policy. Field: " << path << " Value: " << GetValueString(policyValue.value())); 148 } 149 else 150 { 151 auto valueAsString = GetValueString(policyValue.value()); 152 AICLI_LOG(Core, Error, << "Invalid setting from Group Policy. Field: " << path << " Value: " << valueAsString); 153 warnings.emplace_back(StringResource::String::SettingsWarningInvalidValueFromPolicy, path, valueAsString); 154 } 155 156 return; 157 } 158 159 const Json::Path jsonPath(path); 160 Json::Value result = jsonPath.resolve(root); 161 if (!result.isNull()) 162 { 163 auto jsonValue = GetValue<typename details::SettingMapping<S>::json_t>(result); 164 165 if (jsonValue.has_value()) 166 { 167 auto validatedValue = details::SettingMapping<S>::Validate(jsonValue.value()); 168 169 if (validatedValue.has_value()) 170 { 171 // Finally add it to the map 172 settings[S].emplace<details::SettingIndex(S)>( 173 std::forward<typename details::SettingMapping<S>::value_t>(validatedValue.value())); 174 AICLI_LOG(Core, Verbose, << "Valid setting. Field: " << path << " Value: " << GetValueString(jsonValue.value())); 175 } 176 else 177 { 178 auto valueAsString = GetValueString(jsonValue.value()); 179 AICLI_LOG(Core, Error, << "Invalid field value. Field: " << path << " Value: " << valueAsString); 180 warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldValue, path, valueAsString); 181 } 182 } 183 else 184 { 185 AICLI_LOG(Core, Error, << "Invalid field format. Field: " << path << " Using default"); 186 warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldFormat, path); 187 } 188 } 189 else 190 { 191 AICLI_LOG(Core, Verbose, << "Setting " << path << " not found. Using default"); 192 } 193 } 194 195 template <size_t... S> 196 void ValidateAll( 197 Json::Value& root, 198 std::map<Setting, details::SettingVariant>& settings, 199 std::vector<UserSettings::Warning>& warnings, 200 std::index_sequence<S...>) 201 { 202 // Use folding to call each setting validate function. 203 (FoldHelper{}, ..., Validate<static_cast<Setting>(S)>(root, settings, warnings)); 204 } 205 206 std::optional<std::filesystem::path> ValidatePathValue(std::string_view value) 207 { 208 std::filesystem::path path = ConvertToUTF16(value); 209 if (!path.is_absolute()) 210 { 211 return {}; 212 } 213 214 return path; 215 } 216 } 217 218 namespace details 219 { 220 #define WINGET_VALIDATE_SIGNATURE(_setting_) \ 221 std::optional<SettingMapping<Setting::_setting_>::value_t> \ 222 SettingMapping<Setting::_setting_>::Validate(const SettingMapping<Setting::_setting_>::json_t& value) 223 224 // Stamps out a validate function that simply returns the input value. 225 #define WINGET_VALIDATE_PASS_THROUGH(_setting_) \ 226 WINGET_VALIDATE_SIGNATURE(_setting_) \ 227 { \ 228 return value; \ 229 } 230 231 WINGET_VALIDATE_SIGNATURE(AutoUpdateTimeInMinutes) 232 { 233 return std::chrono::minutes(value); 234 } 235 236 WINGET_VALIDATE_SIGNATURE(ProgressBarVisualStyle) 237 { 238 std::string lowerValue = ToLower(value); 239 240 if (value == "accent") 241 { 242 return VisualStyle::Accent; 243 } 244 else if (value == "rainbow") 245 { 246 return VisualStyle::Rainbow; 247 } 248 else if (value == "retro") 249 { 250 return VisualStyle::Retro; 251 } 252 else if (value == "sixel") 253 { 254 return VisualStyle::Sixel; 255 } 256 else if (value == "disabled") 257 { 258 return VisualStyle::Disabled; 259 } 260 261 return {}; 262 } 263 264 WINGET_VALIDATE_PASS_THROUGH(EnableSixelDisplay) 265 WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) 266 WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) 267 WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) 268 WINGET_VALIDATE_PASS_THROUGH(EFResume) 269 WINGET_VALIDATE_PASS_THROUGH(EFFonts) 270 WINGET_VALIDATE_PASS_THROUGH(AnonymizePathForDisplay) 271 WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) 272 WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) 273 WINGET_VALIDATE_PASS_THROUGH(InstallSkipDependencies) 274 WINGET_VALIDATE_PASS_THROUGH(DisableInstallNotes) 275 WINGET_VALIDATE_PASS_THROUGH(UninstallPurgePortablePackage) 276 WINGET_VALIDATE_PASS_THROUGH(NetworkWingetAlternateSourceURL) 277 WINGET_VALIDATE_PASS_THROUGH(MaxResumes) 278 279 #ifndef AICLI_DISABLE_TEST_HOOKS 280 WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) 281 WINGET_VALIDATE_PASS_THROUGH(KeepAllLogFiles) 282 #endif 283 284 WINGET_VALIDATE_SIGNATURE(PortablePackageUserRoot) 285 { 286 return ValidatePathValue(value); 287 } 288 289 WINGET_VALIDATE_SIGNATURE(PortablePackageMachineRoot) 290 { 291 return ValidatePathValue(value); 292 } 293 294 WINGET_VALIDATE_SIGNATURE(ArchiveExtractionMethod) 295 { 296 static constexpr std::string_view s_archiveExtractionMethod_shellApi = "shellApi"; 297 static constexpr std::string_view s_archiveExtractionMethod_tar = "tar"; 298 299 if (Utility::CaseInsensitiveEquals(value, s_archiveExtractionMethod_tar)) 300 { 301 return Archive::ExtractionMethod::Tar; 302 } 303 else if (Utility::CaseInsensitiveEquals(value, s_archiveExtractionMethod_shellApi)) 304 { 305 return Archive::ExtractionMethod::ShellApi; 306 } 307 308 return {}; 309 } 310 311 WINGET_VALIDATE_SIGNATURE(InstallArchitecturePreference) 312 { 313 std::vector<Utility::Architecture> archs; 314 for (auto const& i : value) 315 { 316 Utility::Architecture arch = Utility::ConvertToArchitectureEnum(i); 317 if (Utility::IsApplicableArchitecture(arch) == Utility::InapplicableArchitecture) 318 { 319 return {}; 320 } 321 archs.emplace_back(arch); 322 } 323 return archs; 324 } 325 326 WINGET_VALIDATE_SIGNATURE(InstallArchitectureRequirement) 327 { 328 return SettingMapping<Setting::InstallArchitecturePreference>::Validate(value); 329 } 330 331 WINGET_VALIDATE_SIGNATURE(InstallScopePreference) 332 { 333 static constexpr std::string_view s_scope_user = "user"; 334 static constexpr std::string_view s_scope_machine = "machine"; 335 336 if (Utility::CaseInsensitiveEquals(value, s_scope_user)) 337 { 338 return Manifest::ScopeEnum::User; 339 } 340 else if (Utility::CaseInsensitiveEquals(value, s_scope_machine)) 341 { 342 return Manifest::ScopeEnum::Machine; 343 } 344 345 return {}; 346 } 347 348 WINGET_VALIDATE_SIGNATURE(InstallScopeRequirement) 349 { 350 return SettingMapping<Setting::InstallScopePreference>::Validate(value); 351 } 352 353 WINGET_VALIDATE_SIGNATURE(InstallLocalePreference) 354 { 355 for (auto const& entry : value) 356 { 357 if (!Locale::IsWellFormedBcp47Tag(entry)) 358 { 359 return {}; 360 } 361 } 362 363 return value; 364 } 365 366 WINGET_VALIDATE_SIGNATURE(InstallLocaleRequirement) 367 { 368 return SettingMapping<Setting::InstallLocalePreference>::Validate(value); 369 } 370 371 WINGET_VALIDATE_SIGNATURE(InstallerTypePreference) 372 { 373 std::vector<Manifest::InstallerTypeEnum> installerTypes; 374 for (auto const& i : value) 375 { 376 Manifest::InstallerTypeEnum installerType = Manifest::ConvertToInstallerTypeEnum(i); 377 if (installerType == Manifest::InstallerTypeEnum::Unknown) 378 { 379 return {}; 380 } 381 installerTypes.emplace_back(installerType); 382 } 383 return installerTypes; 384 } 385 386 WINGET_VALIDATE_SIGNATURE(InstallerTypeRequirement) 387 { 388 return SettingMapping<Setting::InstallerTypePreference>::Validate(value); 389 } 390 391 WINGET_VALIDATE_SIGNATURE(InstallDefaultRoot) 392 { 393 return ValidatePathValue(value); 394 } 395 396 WINGET_VALIDATE_SIGNATURE(DownloadDefaultDirectory) 397 { 398 return ValidatePathValue(value); 399 } 400 401 WINGET_VALIDATE_SIGNATURE(ConfigureDefaultModuleRoot) 402 { 403 return ValidatePathValue(value); 404 } 405 406 WINGET_VALIDATE_SIGNATURE(NetworkDownloader) 407 { 408 static constexpr std::string_view s_downloader_default = "default"; 409 static constexpr std::string_view s_downloader_wininet = "wininet"; 410 static constexpr std::string_view s_downloader_do = "do"; 411 412 if (Utility::CaseInsensitiveEquals(value, s_downloader_default)) 413 { 414 return InstallerDownloader::Default; 415 } 416 else if (Utility::CaseInsensitiveEquals(value, s_downloader_wininet)) 417 { 418 return InstallerDownloader::WinInet; 419 } 420 else if (Utility::CaseInsensitiveEquals(value, s_downloader_do)) 421 { 422 return InstallerDownloader::DeliveryOptimization; 423 } 424 425 return {}; 426 } 427 428 WINGET_VALIDATE_SIGNATURE(NetworkDOProgressTimeoutInSeconds) 429 { 430 return std::chrono::seconds(value); 431 } 432 433 WINGET_VALIDATE_SIGNATURE(LoggingLevelPreference) 434 { 435 // logging preference possible values 436 static constexpr std::string_view s_logging_verbose = "verbose"; 437 static constexpr std::string_view s_logging_info = "info"; 438 static constexpr std::string_view s_logging_warning = "warning"; 439 static constexpr std::string_view s_logging_error = "error"; 440 static constexpr std::string_view s_logging_critical = "critical"; 441 442 if (Utility::CaseInsensitiveEquals(value, s_logging_verbose)) 443 { 444 return Level::Verbose; 445 } 446 else if (Utility::CaseInsensitiveEquals(value, s_logging_info)) 447 { 448 return Level::Info; 449 } 450 else if (Utility::CaseInsensitiveEquals(value, s_logging_warning)) 451 { 452 return Level::Warning; 453 } 454 else if (Utility::CaseInsensitiveEquals(value, s_logging_error)) 455 { 456 return Level::Error; 457 } 458 else if (Utility::CaseInsensitiveEquals(value, s_logging_critical)) 459 { 460 return Level::Crit; 461 } 462 return {}; 463 } 464 465 WINGET_VALIDATE_SIGNATURE(LoggingChannelPreference) 466 { 467 Logging::Channel result = Logging::Channel::None; 468 469 for (auto const& entry : value) 470 { 471 result |= GetChannelFromName(entry); 472 } 473 474 return result; 475 } 476 } 477 478 #ifndef AICLI_DISABLE_TEST_HOOKS 479 static UserSettings* s_UserSettings_Override = nullptr; 480 481 void SetUserSettingsOverride(UserSettings* value) 482 { 483 s_UserSettings_Override = value; 484 } 485 #endif 486 487 static std::atomic_bool s_userSettingsInitialized{ false }; 488 static std::atomic_bool s_userSettingsInInitialization{ false }; 489 490 UserSettings const& UserSettings::Instance(const std::optional<std::string>& content) 491 { 492 #ifndef AICLI_DISABLE_TEST_HOOKS 493 if (s_UserSettings_Override) 494 { 495 return *s_UserSettings_Override; 496 } 497 #endif 498 if (!s_userSettingsInitialized) 499 { 500 s_userSettingsInInitialization = true; 501 } 502 503 static UserSettings userSettings(content); 504 s_userSettingsInitialized = true; 505 s_userSettingsInInitialization = false; 506 507 return userSettings; 508 } 509 510 const UserSettings* TryGetUser() 511 { 512 if (s_userSettingsInitialized) 513 { 514 return &UserSettings::Instance(); 515 } 516 517 // Try to initialize UserSettings, return nullptr if it's already in initialization. 518 if (s_userSettingsInInitialization) 519 { 520 return nullptr; 521 } 522 523 return &UserSettings::Instance(); 524 } 525 526 UserSettings const& User() 527 { 528 return UserSettings::Instance(); 529 } 530 531 bool TryInitializeCustomUserSettings(std::string content) 532 { 533 if (s_userSettingsInitialized || s_userSettingsInInitialization) 534 { 535 return false; 536 } 537 538 return UserSettings::Instance(std::move(content)).GetType() == UserSettingsType::Custom; 539 } 540 541 UserSettings::UserSettings(const std::optional<std::string>& content) : m_type(UserSettingsType::Default) 542 { 543 Json::Value settingsRoot = Json::Value::nullSingleton(); 544 545 // Settings can be loaded from settings.json or settings.json.backup files. 546 // 0 - Use default (empty) settings if disabled by group policy. 547 // if 548 // 1 - Use passed in settings content if available. 549 // else 550 // 2 - Use settings.json if exists and passes parsing. 551 // 3 - Use settings.backup.json if settings.json fails to parse. 552 // finally 553 // 4 - Use default (empty) if both settings files fail to load. 554 555 if (!GroupPolicies().IsEnabled(TogglePolicy::Policy::Settings)) 556 { 557 AICLI_LOG(Core, Info, << "Ignoring settings file due to group policy. Using default values."); 558 return; 559 } 560 561 if (content.has_value()) 562 { 563 auto settingsJson = ParseSettingsContent(content.value(), "CustomSettings", m_warnings); 564 if (settingsJson.has_value()) 565 { 566 AICLI_LOG(Core, Info, << "Settings loaded from custom settings"); 567 m_type = UserSettingsType::Custom; 568 settingsRoot = settingsJson.value(); 569 } 570 } 571 else 572 { 573 auto settingsJson = ParseFile(Stream::PrimaryUserSettings, m_warnings); 574 if (settingsJson.has_value()) 575 { 576 AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::PrimaryUserSettings.Name); 577 m_type = UserSettingsType::Standard; 578 settingsRoot = settingsJson.value(); 579 } 580 581 // Settings didn't parse or doesn't exist, try with backup. 582 if (settingsRoot.isNull()) 583 { 584 auto settingsBackupJson = ParseFile(Stream::BackupUserSettings, m_warnings); 585 if (settingsBackupJson.has_value()) 586 { 587 AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::BackupUserSettings.Name); 588 m_warnings.emplace_back(StringResource::String::SettingsWarningLoadedBackupSettings); 589 m_type = UserSettingsType::Backup; 590 settingsRoot = settingsBackupJson.value(); 591 } 592 else 593 { 594 // Settings and back up didn't parse or exist. If they exist then warn the user. 595 auto settingsPath = Stream{ Stream::PrimaryUserSettings }.GetPath(); 596 auto backupPath = Stream{ Stream::BackupUserSettings }.GetPath(); 597 if (std::filesystem::exists(settingsPath) || std::filesystem::exists(backupPath)) 598 { 599 m_warnings.emplace_back(StringResource::String::SettingsWarningUsingDefault); 600 } 601 } 602 } 603 } 604 605 if (!settingsRoot.isNull()) 606 { 607 ValidateAll(settingsRoot, m_settings, m_warnings, std::make_index_sequence<static_cast<size_t>(Setting::Max)>()); 608 } 609 else 610 { 611 AICLI_LOG(Core, Info, << "Valid settings file not found. Using default values."); 612 } 613 } 614 615 void UserSettings::PrepareToShellExecuteFile() const 616 { 617 UserSettingsType userSettingType = GetType(); 618 619 if (userSettingType == UserSettingsType::Default) 620 { 621 Stream primarySettings{ Stream::PrimaryUserSettings }; 622 623 // Create settings file if it doesn't exist. 624 if (!std::filesystem::exists(primarySettings.GetPath())) 625 { 626 std::ignore = primarySettings.Set(s_SettingEmpty); 627 AICLI_LOG(Core, Info, << "Created new settings file"); 628 } 629 } 630 else if (userSettingType == UserSettingsType::Standard) 631 { 632 // Settings file was loaded correctly, create backup. 633 auto from = SettingsFilePath(); 634 auto to = Stream{ Stream::BackupUserSettings }.GetPath(); 635 std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); 636 AICLI_LOG(Core, Info, << "Copied settings to backup file"); 637 } 638 } 639 640 std::filesystem::path UserSettings::SettingsFilePath(bool forDisplay) 641 { 642 auto path = Stream{ Stream::PrimaryUserSettings }.GetPath(); 643 644 if (forDisplay && Settings::User().Get<Setting::AnonymizePathForDisplay>()) 645 { 646 ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%"); 647 } 648 649 return path; 650 } 651 }