winget-cli

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

commit d82f5876a81abf979e9328f0a1823b7952d12466
parent 56e210b30e42ad26b3750a7ea8a1e1b6eacc5d8d
Author: Ashwini Patil <47225815+ashpatil-msft@users.noreply.github.com>
Date:   Mon, 30 Aug 2021 22:33:10 -0700

Integrating custom header with V1.1 changes (#1422)

Integrating custom header with V1.1 changes
Diffstat:
M.github/actions/spelling/allow.txt | 2++
Msrc/AppInstallerCLICore/Workflows/SourceFlow.cpp | 2--
Msrc/AppInstallerCLIE2ETests/Constants.cs | 1+
Msrc/AppInstallerCLITests/CustomHeader.cpp | 51++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/AppInstallerCLITests/RestInterface_1_1.cpp | 18+++++++++---------
Msrc/AppInstallerCommonCore/Errors.cpp | 2++
Msrc/AppInstallerCommonCore/Public/AppInstallerErrors.h | 1+
Msrc/AppInstallerRepositoryCore/Rest/RestClient.cpp | 8++++----
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp | 12++++++++++--
10 files changed, 80 insertions(+), 19 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -78,6 +78,7 @@ cref csproj CStr CURSORPOSITON +CUSTOMHEADER cwctype datatelemetry dbconn @@ -249,6 +250,7 @@ MAKEINTRESOURCE makemsix MANIFESTSCHEMA MANIFESTVERSION +MAXLENGTH MBs mday memset diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp @@ -90,8 +90,6 @@ namespace AppInstaller::CLI::Workflow sourceDetails.Type = context.Args.GetArg(Args::Type::SourceType); } - sourceDetails.CustomHeader = GetCustomHeaderFromArg(context, sourceDetails); - context.Reporter.Info() << Resource::String::SourceAddBegin << std::endl << " "_liv << sourceDetails.Name << " -> "_liv << sourceDetails.Arg << std::endl; diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -137,6 +137,7 @@ namespace AppInstallerCLIE2ETests public const int ERROR_RESTSOURCE_ENDPOINT_NOT_FOUND = unchecked((int)0x8a150044); public const int ERROR_SOURCE_OPEN_FAILED = unchecked((int)0x8a150045); public const int ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED = unchecked((int)0x8a150046); + public const int ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH = unchecked((int)0x8a150047); } } } diff --git a/src/AppInstallerCLITests/CustomHeader.cpp b/src/AppInstallerCLITests/CustomHeader.cpp @@ -6,7 +6,7 @@ #include "TestSettings.h" #include "TestSource.h" #include "TestRestRequestHandler.h" -#include <Rest/Schema/1_0/Interface.h> +#include <Rest/Schema/1_1/Interface.h> #include <Rest/Schema/JsonHelper.h> #include <Rest/RestClient.h> #include <winget/Settings.h> @@ -161,3 +161,52 @@ TEST_CASE("CreateSource_CustomHeaderNotApplicable", "[RestSource][CustomHeader]" auto source = OpenSourceFromDetails(details, progress).Source; REQUIRE(!source.get()->GetDetails().CustomHeader.has_value()); } + +TEST_CASE("RestSourceSearch_CustomHeader", "[RestSource][CustomHeader]") +{ + utility::string_t customHeader = L"Testing custom header"; + auto header = std::make_pair<>(CustomHeaderName, customHeader); + HttpClientHelper helper{ GetCustomHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + std::unordered_map<utility::string_t, utility::string_t> headers; + headers.emplace(CustomHeaderName, customHeader); + + V1_1::Interface v1_1{ "https://restsource.com/api", {}, headers, std::move(helper) }; + Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); + REQUIRE(searchResponse.Matches.size() == 1); + Schema::IRestClient::Package package = searchResponse.Matches.at(0); +} + +TEST_CASE("RestSourceSearch_WhitespaceCustomHeader", "[RestSource][CustomHeader]") +{ + utility::string_t customHeader = L" "; + auto header = std::make_pair<>(CustomHeaderName, customHeader); + HttpClientHelper helper{ GetCustomHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + std::unordered_map<utility::string_t, utility::string_t> headers; + headers.emplace(CustomHeaderName, customHeader); + + V1_1::Interface v1_1{ "https://restsource.com/api", {}, headers, std::move(helper) }; + Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); + REQUIRE(searchResponse.Matches.size() == 1); +} + +TEST_CASE("RestSourceSearch_NoCustomHeader", "[RestSource][CustomHeader]") +{ + utility::string_t customHeader = L" "; + auto header = std::make_pair<>(CustomHeaderName, customHeader); + HttpClientHelper helper{ GetCustomHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + std::unordered_map<utility::string_t, utility::string_t> headers; + headers.emplace(CustomHeaderName, customHeader); + + V1_1::Interface v1_1{ "https://restsource.com/api", {}, {}, std::move(helper) }; + REQUIRE_THROWS_HR(v1_1.Search({}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INTERNAL_ERROR); +} + +TEST_CASE("RestSourceSearch_CustomHeaderExceedingSize", "[RestSource][CustomHeader]") +{ + std::string customHeader = "This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. "; + auto header = std::make_pair<>(CustomHeaderName, JsonHelper::GetUtilityString(customHeader)); + HttpClientHelper helper{ GetCustomHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + + REQUIRE_THROWS_HR(RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, std::move(helper)), + APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_1.cpp b/src/AppInstallerCLITests/RestInterface_1_1.cpp @@ -42,7 +42,7 @@ TEST_CASE("Search_BadResponse_UnsupportedPackageMatchFields", "[RestSource][Inte })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -58,7 +58,7 @@ TEST_CASE("Search_BadResponse_RequiredPackageMatchFields", "[RestSource][Interfa })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -74,7 +74,7 @@ TEST_CASE("GetManifests_BadResponse_UnsupportedQueryParameters", "[RestSource][I })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); } @@ -87,7 +87,7 @@ TEST_CASE("GetManifests_BadResponse_RequiredQueryParameters", "[RestSource][Inte })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); } @@ -107,7 +107,7 @@ TEST_CASE("Search_BadRequest_UnsupportedPackageMatchFields", "[RestSource][Inter })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Moniker, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -130,7 +130,7 @@ TEST_CASE("Search_GoodRequest_OnlyMarketRequired", "[RestSource][Interface_1_1]" })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -175,7 +175,7 @@ TEST_CASE("GetManifests_BadRequest_UnsupportedQueryParameters", "[RestSource][In })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; REQUIRE_THROWS_HR(v1_1.GetManifestByVersion("Foo", "1.0", "beta"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); } @@ -211,7 +211,7 @@ TEST_CASE("GetManifests_GoodRequest_OnlyMarketRequired", "[RestSource][Interface IRestClient::Information info = GetTestSourceInformation(); info.UnsupportedQueryParameters.clear(); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, info, std::move(helper) }; + Interface v1_1{ TestRestUriString, info, {}, std::move(helper) }; auto manifestResult = v1_1.GetManifestByVersion("Foo", "5.0.0", ""); REQUIRE(manifestResult.has_value()); const Manifest& manifest = manifestResult.value(); @@ -258,7 +258,7 @@ TEST_CASE("GetManifests_GoodResponse_MSStoreType", "[RestSource][Interface_1_1]" })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(msstoreInstallerResponse)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), std::move(helper) }; + Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; std::vector<Manifest> manifests = v1_1.GetManifests("Foo.Bar"); REQUIRE(manifests.size() == 1); diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp @@ -154,6 +154,8 @@ namespace AppInstaller return "Failed to open the source."; case APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED: return "Source agreements were not agreed to"; + case APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH: + return "Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again."; default: return "Unknown Error Code"; } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -84,6 +84,7 @@ #define APPINSTALLER_CLI_ERROR_RESTSOURCE_ENDPOINT_NOT_FOUND ((HRESULT)0x8a150044) #define APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED ((HRESULT)0x8a150045) #define APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED ((HRESULT)0x8a150046) +#define APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH ((HRESULT)0x8a150047) namespace AppInstaller diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -20,6 +20,7 @@ namespace AppInstaller::Repository::Rest std::set<Version> WingetSupportedContracts = { Version_1_0_0, Version_1_1_0 }; constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv; + constexpr size_t WindowsPackageManagerHeaderMaxLength = 1024; namespace { std::unordered_map<utility::string_t, utility::string_t> GetHeaders(std::optional<std::string> customHeader) @@ -30,6 +31,8 @@ namespace AppInstaller::Repository::Rest return {}; } + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH, customHeader.value().size() > WindowsPackageManagerHeaderMaxLength); + std::unordered_map<utility::string_t, utility::string_t> headers; headers.emplace(JsonHelper::GetUtilityString(WindowsPackageManagerHeader), JsonHelper::GetUtilityString(customHeader.value())); return headers; @@ -125,12 +128,9 @@ namespace AppInstaller::Repository::Rest } else if (version == Version_1_1_0) { - return std::make_unique<Schema::V1_1::Interface>(api, information); + return std::make_unique<Schema::V1_1::Interface>(api, information, additionalHeaders); } - // TODO: USE additionalHeaders with V1.1 changes. - (void)additionalHeaders; - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h @@ -8,7 +8,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_1 // Interface to this schema version exposed through IRestClient. struct Interface : public V1_0::Interface { - Interface(const std::string& restApi, IRestClient::Information information, const HttpClientHelper& httpClientHelper = {}); + Interface(const std::string& restApi, IRestClient::Information information, const std::unordered_map<utility::string_t, utility::string_t>& additionalHeaders = {}, const HttpClientHelper& httpClientHelper = {}); Interface(const Interface&) = delete; Interface& operator=(const Interface&) = delete; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp @@ -27,10 +27,18 @@ namespace AppInstaller::Repository::Rest::Schema::V1_1 constexpr std::string_view RequiredQueryParameters = "RequiredQueryParameters"sv; } - Interface::Interface(const std::string& restApi, IRestClient::Information information, const HttpClientHelper& httpClientHelper) : - V1_0::Interface(restApi, httpClientHelper), m_information(std::move(information)) + Interface::Interface( + const std::string& restApi, + IRestClient::Information information, + const std::unordered_map<utility::string_t, utility::string_t>& additionalHeaders, + const HttpClientHelper& httpClientHelper) : V1_0::Interface(restApi, httpClientHelper), m_information(std::move(information)) { m_requiredRestApiHeaders[JsonHelper::GetUtilityString(ContractVersion)] = JsonHelper::GetUtilityString(Version_1_1_0.ToString()); + + if (!additionalHeaders.empty()) + { + m_requiredRestApiHeaders.insert(additionalHeaders.begin(), additionalHeaders.end()); + } } Utility::Version Interface::GetVersion() const