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 }