commit f33afcb3e76cd70cbd5cec94a569378804b23757
parent 9dcbf99b2831af395fc996917da7dfe8fc085ffc
Author: JohnMcPMS <johnmcp@microsoft.com>
Date: Thu, 20 Jan 2022 14:26:49 -0800
Add a default user agent to REST source calls (#1839)
Provides a default user agent header in the http helper class that the REST source code uses. It can be overridden by providing the user agent header in the headers argument.
The default format is:
```
winget-cli WindowsPackageManager/1.2.3 DesktopAppInstaller/10.11.12
```
where `1.2.3` is the client version as would be output by `winget -v` (without the `v` at the beginning) and `10.11.12` is the package version of the MSIX.
Also updates the WinINET downloader to use the same user agent string, but it is not overridable.
Diffstat:
9 files changed, 83 insertions(+), 3 deletions(-)
diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln
@@ -650,7 +650,6 @@ Global
{1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|ARM.ActiveCfg = Fuzzing|x64
{1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|ARM64.ActiveCfg = Fuzzing|x64
{1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|x64.ActiveCfg = Debug|x64
- {1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|x64.Build.0 = Debug|x64
{1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|x86.ActiveCfg = Fuzzing|x64
{1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|x64
{1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|ARM.ActiveCfg = Fuzzing|x64
diff --git a/src/AppInstallerCLITests/HttpClientHelper.cpp b/src/AppInstallerCLITests/HttpClientHelper.cpp
@@ -5,8 +5,12 @@
#include "TestRestRequestHandler.h"
#include <AppInstallerErrors.h>
#include <Rest/Schema/HttpClientHelper.h>
+#include <AppInstallerRuntime.h>
+#include <AppInstallerStrings.h>
using namespace AppInstaller::Repository::Rest::Schema;
+using namespace AppInstaller::Runtime;
+using namespace AppInstaller::Utility;
TEST_CASE("ExtractJsonResponse_UnsupportedMimeType", "[RestSource][RestSearch]")
{
@@ -25,3 +29,30 @@ TEST_CASE("ValidateAndExtractResponse_NotFound", "[RestSource]")
HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) };
REQUIRE_THROWS_HR(helper.HandleGet(L"https://testUri"), APPINSTALLER_CLI_ERROR_RESTSOURCE_ENDPOINT_NOT_FOUND);
}
+
+TEST_CASE("EnsureDefaultUserAgent", "[RestSource]")
+{
+ HttpClientHelper helper{ GetTestRestRequestHandler([](const web::http::http_request& request)
+ {
+ auto itr = request.headers().find(web::http::header_names::user_agent);
+ if (itr != request.headers().end() &&
+ itr->second.find(ConvertToUTF16(GetClientVersion())) != utility::string_t::npos &&
+ itr->second.find(ConvertToUTF16(GetPackageVersion())) != utility::string_t::npos)
+ {
+ return web::http::status_codes::OK;
+ }
+ else
+ {
+ return web::http::status_codes::BadRequest;
+ }
+ }) };
+
+ SECTION("GET")
+ {
+ REQUIRE_NOTHROW(helper.HandleGet(L"https://testUri"));
+ }
+ SECTION("POST")
+ {
+ REQUIRE_NOTHROW(helper.HandlePost(L"https://testUri", {}));
+ }
+}
diff --git a/src/AppInstallerCLITests/TestRestRequestHandler.cpp b/src/AppInstallerCLITests/TestRestRequestHandler.cpp
@@ -27,3 +27,18 @@ std::shared_ptr<TestRestRequestHandler> GetTestRestRequestHandler(
return pplx::task_from_result(response);
});
}
+
+std::shared_ptr<TestRestRequestHandler> GetTestRestRequestHandler(
+ std::function<web::http::status_code(const web::http::http_request& request)> handler)
+{
+ return std::make_shared<TestRestRequestHandler>([handler = std::move(handler)](web::http::http_request request) ->
+ pplx::task<web::http::http_response>
+ {
+ web::http::http_response response;
+ response.set_body(utf16string{});
+
+ response.headers().set_content_type(web::http::details::mime_types::application_json);
+ response.set_status_code(handler(request));
+ return pplx::task_from_result(response);
+ });
+}
diff --git a/src/AppInstallerCLITests/TestRestRequestHandler.h b/src/AppInstallerCLITests/TestRestRequestHandler.h
@@ -19,3 +19,6 @@ private:
std::shared_ptr<TestRestRequestHandler> GetTestRestRequestHandler(
const web::http::status_code statusCode, const utility::string_t& sampleResponseString = {}, const utility::string_t& mimeType = web::http::details::mime_types::application_json);
+
+std::shared_ptr<TestRestRequestHandler> GetTestRestRequestHandler(
+ std::function<web::http::status_code(const web::http::http_request& request)> handler);
diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp
@@ -30,7 +30,7 @@ namespace AppInstaller::Utility
AICLI_LOG(Core, Info, << "WinINet downloading from url: " << url);
wil::unique_hinternet session(InternetOpenA(
- "winget-cli",
+ Runtime::GetDefaultUserAgent().get().c_str(),
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h
@@ -66,4 +66,7 @@ namespace AppInstaller::Runtime
// Returns true if this is a release build; false if not.
inline constexpr bool IsReleaseBuild();
+
+ // Gets the default user agent string for the Windows Package Manager.
+ Utility::LocIndString GetDefaultUserAgent();
}
diff --git a/src/AppInstallerCommonCore/Public/winget/LocIndependent.h b/src/AppInstallerCommonCore/Public/winget/LocIndependent.h
@@ -35,7 +35,8 @@ namespace AppInstaller::Utility
bool empty() const { return m_value.empty(); }
- const std::string& get() const { return m_value; }
+ const std::string& get() const & { return m_value; }
+ std::string&& get() && { return std::move(m_value); }
operator const std::string& () const { return m_value; }
operator std::string_view() const { return m_value; }
diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp
@@ -473,6 +473,18 @@ namespace AppInstaller::Runtime
#endif
}
+ // Using "standard" user agent format
+ // Keeping `winget-cli` for historical reasons
+ Utility::LocIndString GetDefaultUserAgent()
+ {
+ std::ostringstream strstr;
+ strstr <<
+ "winget-cli"
+ " WindowsPackageManager/" << GetClientVersion() <<
+ " DesktopAppInstaller/" << GetPackageVersion();
+ return Utility::LocIndString{ strstr.str() };
+ }
+
#ifndef AICLI_DISABLE_TEST_HOOKS
void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path)
{
diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.cpp
@@ -5,6 +5,20 @@
namespace AppInstaller::Repository::Rest::Schema
{
+ namespace
+ {
+ // If the caller does not pass in a user agent header, put the default one on the request.
+ void EnsureDefaultUserAgent(web::http::http_request& request)
+ {
+ static utility::string_t c_defaultUserAgent = Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent());
+
+ if (!request.headers().has(web::http::header_names::user_agent))
+ {
+ request.headers().add(web::http::header_names::user_agent, c_defaultUserAgent);
+ }
+ }
+ }
+
HttpClientHelper::HttpClientHelper(std::optional<std::shared_ptr<web::http::http_pipeline_stage>> stage) : m_defaultRequestHandlerStage(stage) {}
pplx::task<web::http::http_response> HttpClientHelper::Post(
@@ -21,6 +35,7 @@ namespace AppInstaller::Repository::Rest::Schema
{
request.headers().add(pair.first, pair.second);
}
+ EnsureDefaultUserAgent(request);
AICLI_LOG(Repo, Verbose, << "Http POST request details:\n" << utility::conversions::to_utf8string(request.to_string()));
@@ -52,6 +67,7 @@ namespace AppInstaller::Repository::Rest::Schema
{
request.headers().add(pair.first, pair.second);
}
+ EnsureDefaultUserAgent(request);
AICLI_LOG(Repo, Verbose, << "Http GET request details:\n" << utility::conversions::to_utf8string(request.to_string()));