winget-cli

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

RestClient.cpp (9573B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "RestClient.h"
      5 #include "Rest/Schema/1_0/Interface.h"
      6 #include "Rest/Schema/1_1/Interface.h"
      7 #include "Rest/Schema/1_4/Interface.h"
      8 #include "Rest/Schema/1_5/Interface.h"
      9 #include "Rest/Schema/1_6/Interface.h"
     10 #include "Rest/Schema/1_7/Interface.h"
     11 #include "Rest/Schema/1_9/Interface.h"
     12 #include "Rest/Schema/1_10/Interface.h"
     13 #include "Rest/Schema/1_12/Interface.h"
     14 #include "Rest/Schema/InformationResponseDeserializer.h"
     15 #include "Rest/Schema/CommonRestConstants.h"
     16 #include <winget/HttpClientHelper.h>
     17 #include <winget/Rest.h>
     18 #include <winget/JsonUtil.h>
     19 
     20 using namespace AppInstaller::Repository::Rest::Schema;
     21 using namespace AppInstaller::Repository::Rest::Schema::V1_0;
     22 using namespace AppInstaller::Utility;
     23 using namespace AppInstaller::Http;
     24 
     25 namespace AppInstaller::Repository::Rest
     26 {
     27     // Supported versions
     28     std::set<Version> WingetSupportedContracts = {
     29         Version_1_0_0,
     30         Version_1_1_0,
     31         Version_1_4_0,
     32         Version_1_5_0,
     33         Version_1_6_0,
     34         Version_1_7_0,
     35         Version_1_9_0,
     36         Version_1_10_0,
     37         Version_1_12_0,
     38     };
     39 
     40     constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv;
     41     constexpr size_t WindowsPackageManagerHeaderMaxLength = 1024;
     42 
     43     namespace
     44     {
     45         HttpClientHelper::HttpRequestHeaders GetHeaders(const std::optional<std::string>& customHeader, std::string_view caller)
     46         {
     47             HttpClientHelper::HttpRequestHeaders headers;
     48 
     49             if (customHeader)
     50             {
     51                 AICLI_LOG(Repo, Verbose, << "Custom header found: " << customHeader.value());
     52                 THROW_HR_IF(APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH, customHeader.value().size() > WindowsPackageManagerHeaderMaxLength);
     53                 headers.emplace(JSON::GetUtilityString(WindowsPackageManagerHeader), JSON::GetUtilityString(customHeader.value()));
     54             }
     55 
     56             if (!caller.empty())
     57             {
     58                 AICLI_LOG(Repo, Verbose, << "User agent caller found: " << caller);
     59                 std::wstring userAgentWide = JSON::GetUtilityString(Runtime::GetUserAgent(caller));
     60                 try
     61                 {
     62                     // Replace user profile if the caller binary is under user profile.
     63                     userAgentWide = Utility::ReplaceWhileCopying(userAgentWide, Runtime::GetPathTo(Runtime::PathName::UserProfile).wstring(), L"%USERPROFILE%");
     64                 }
     65                 CATCH_LOG();
     66                 headers.emplace(web::http::header_names::user_agent, userAgentWide);
     67             }
     68 
     69             return headers;
     70         }
     71 
     72         IRestClient::Information GetInformationInternal(
     73             const utility::string_t& restApi, const HttpClientHelper::HttpRequestHeaders& additionalHeaders, const HttpClientHelper& clientHelper)
     74         {
     75             // Call information endpoint
     76             utility::string_t endpoint = AppInstaller::Rest::AppendPathToUri(restApi, JSON::GetUtilityString(InformationGetEndpoint));
     77             std::optional<web::json::value> response = clientHelper.HandleGet(endpoint, additionalHeaders);
     78 
     79             THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !response);
     80 
     81             InformationResponseDeserializer responseDeserializer;
     82             IRestClient::Information information = responseDeserializer.Deserialize(response.value());
     83 
     84             return information;
     85         }
     86     }
     87 
     88     RestClient::RestClient(std::unique_ptr<Schema::IRestClient> supportedInterface, std::string sourceIdentifier)
     89         : m_interface(std::move(supportedInterface)), m_sourceIdentifier(std::move(sourceIdentifier))
     90     {
     91     }
     92 
     93     std::optional<Manifest::Manifest> RestClient::GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const
     94     {
     95         return m_interface->GetManifestByVersion(packageId, version, channel);
     96     }
     97 
     98     IRestClient::SearchResult RestClient::Search(const SearchRequest& request) const
     99     {
    100         return m_interface->Search(request);
    101     }
    102 
    103     std::string RestClient::GetSourceIdentifier() const
    104     {
    105         return m_sourceIdentifier;
    106     }
    107 
    108     IRestClient::Information RestClient::GetSourceInformation() const
    109     {
    110         return m_interface->GetSourceInformation();
    111     }
    112 
    113     std::optional<Version> RestClient::GetLatestCommonVersion(
    114         const std::vector<std::string>& serverSupportedVersions,
    115         const std::set<Version>& wingetSupportedVersions)
    116     {
    117         std::set<Version> commonVersions;
    118         for (auto& version : serverSupportedVersions)
    119         {
    120             Version versionInfo(version);
    121             auto itr = std::find_if(wingetSupportedVersions.begin(), wingetSupportedVersions.end(),
    122                 [&](const Version& v)
    123                 {
    124                     // Only check major and minor version match if applicable
    125                     if (v.GetParts().size() >= 2)
    126                     {
    127                         return versionInfo.GetParts().size() >= 2 &&
    128                             versionInfo.GetParts().at(0) == v.GetParts().at(0) &&
    129                             versionInfo.GetParts().at(1) == v.GetParts().at(1);
    130                     }
    131                     else
    132                     {
    133                         return versionInfo == v;
    134                     }
    135                 });
    136             if (itr != wingetSupportedVersions.end())
    137             {
    138                 commonVersions.insert(*itr);
    139             }
    140         }
    141 
    142         if (commonVersions.empty())
    143         {
    144             return {};
    145         }
    146 
    147         return *commonVersions.rbegin();
    148     }
    149 
    150     Schema::IRestClient::Information RestClient::GetInformation(const std::string& restApi, const std::optional<std::string>& customHeader, std::string_view caller, const HttpClientHelper& helper)
    151     {
    152         utility::string_t restEndpoint = AppInstaller::Rest::GetRestAPIBaseUri(restApi);
    153         THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(restEndpoint));
    154 
    155         auto headers = GetHeaders(customHeader, caller);
    156 
    157         return GetInformationInternal(restEndpoint, headers, helper);
    158     }
    159 
    160     std::unique_ptr<Schema::IRestClient> RestClient::GetSupportedInterface(
    161         const std::string& api,
    162         const HttpClientHelper::HttpRequestHeaders& additionalHeaders,
    163         const IRestClient::Information& information,
    164         const Authentication::AuthenticationArguments& authArgs,
    165         const Version& version,
    166         const HttpClientHelper& helper)
    167     {
    168         if (version == Version_1_0_0)
    169         {
    170             return std::make_unique<Schema::V1_0::Interface>(api, helper);
    171         }
    172         else if (version == Version_1_1_0)
    173         {
    174             return std::make_unique<Schema::V1_1::Interface>(api, helper, information, additionalHeaders);
    175         }
    176         else if (version == Version_1_4_0)
    177         {
    178             return std::make_unique<Schema::V1_4::Interface>(api, helper, information, additionalHeaders);
    179         }
    180         else if (version == Version_1_5_0)
    181         {
    182             return std::make_unique<Schema::V1_5::Interface>(api, helper, information, additionalHeaders);
    183         }
    184         else if (version == Version_1_6_0)
    185         {
    186             return std::make_unique<Schema::V1_6::Interface>(api, helper, information, additionalHeaders);
    187         }
    188         else if (version == Version_1_7_0)
    189         {
    190             return std::make_unique<Schema::V1_7::Interface>(api, helper, information, additionalHeaders, authArgs);
    191         }
    192         else if (version == Version_1_9_0)
    193         {
    194             return std::make_unique<Schema::V1_9::Interface>(api, helper, information, additionalHeaders, authArgs);
    195         }
    196         else if (version == Version_1_10_0)
    197         {
    198             return std::make_unique<Schema::V1_10::Interface>(api, helper, information, additionalHeaders, authArgs);
    199         }
    200         else if (version == Version_1_12_0)
    201         {
    202             return std::make_unique<Schema::V1_12::Interface>(api, helper, information, additionalHeaders, authArgs);
    203         }
    204 
    205         THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION);
    206     }
    207 
    208     RestClient RestClient::Create(
    209         const std::string& restApi,
    210         const std::optional<std::string>& customHeader,
    211         std::string_view caller,
    212         const HttpClientHelper& helper,
    213         const Schema::IRestClient::Information& information,
    214         const Authentication::AuthenticationArguments& authArgs)
    215     {
    216         utility::string_t restEndpoint = AppInstaller::Rest::GetRestAPIBaseUri(restApi);
    217         THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(restEndpoint));
    218 
    219         auto headers = GetHeaders(customHeader, caller);
    220 
    221         std::optional<Version> latestCommonVersion = GetLatestCommonVersion(information.ServerSupportedVersions, WingetSupportedContracts);
    222         THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !latestCommonVersion);
    223 
    224         std::unique_ptr<Schema::IRestClient> supportedInterface = GetSupportedInterface(utility::conversions::to_utf8string(restEndpoint), headers, information, authArgs, latestCommonVersion.value(), helper);
    225         return RestClient{ std::move(supportedInterface), information.SourceIdentifier };
    226     }
    227 }