winget-cli

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

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 }