Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Set User-Agent header in Origin Policy filter (#10695)",
"packageName": "react-native-windows",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}
120 changes: 119 additions & 1 deletion vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <Networking/IHttpResource.h>
#include <Networking/OriginPolicy.h>
#include <RuntimeOptions.h>
#include <Test/HttpServer.h>
#include <unicode.h>

Expand Down Expand Up @@ -40,7 +41,10 @@ TEST_CLASS (HttpResourceIntegrationTest) {
static uint16_t s_port;

TEST_METHOD_CLEANUP(MethodCleanup) {
// Bug in WebSocketServer does not correctly release TCP port between test methods.
// Clear any runtime options that may be used by tests in this class.
MicrosoftReactSetRuntimeOptionString("Http.UserAgent", nullptr);

// Bug in test HTTP server does not correctly release TCP port between test methods.
// Using a different por per test for now.
s_port++;
}
Expand Down Expand Up @@ -164,6 +168,120 @@ TEST_CLASS (HttpResourceIntegrationTest) {
}
}

TEST_METHOD(RequestGetExplicitUserAgentSucceeds) {
string url = "https://api.github.com/repos/microsoft/react-native-xaml";

promise<void> rcPromise;
string error;
IHttpResource::Response response;

auto resource = IHttpResource::Make();
resource->SetOnResponse([&rcPromise, &response](int64_t, IHttpResource::Response callbackResponse) {
response = callbackResponse;
rcPromise.set_value();
});
resource->SetOnError([&rcPromise, &error](int64_t, string &&message, bool) {
error = std::move(message);
rcPromise.set_value();
});

//clang-format off
resource->SendRequest(
"GET",
std::move(url),
0, /*requestId*/
{{"User-Agent", "React Native Windows"}},
{}, /*data*/
"text", /*responseType*/
false,
0 /*timeout*/,
false /*withCredentials*/,
[](int64_t) {});
//clang-format on

rcPromise.get_future().wait();

Assert::AreEqual({}, error, L"Error encountered");
Assert::AreEqual(static_cast<int64_t>(200), response.StatusCode);
}

TEST_METHOD(RequestGetImplicitUserAgentSucceeds) {
string url = "https://api.github.com/repos/microsoft/react-native-windows";

promise<void> rcPromise;
string error;
IHttpResource::Response response;

auto resource = IHttpResource::Make();
resource->SetOnResponse([&rcPromise, &response](int64_t, IHttpResource::Response callbackResponse) {
response = callbackResponse;
rcPromise.set_value();
});
resource->SetOnError([&rcPromise, &error](int64_t, string &&message, bool) {
error = std::move(message);
rcPromise.set_value();
});

MicrosoftReactSetRuntimeOptionString("Http.UserAgent", "React Native Windows");

//clang-format off
resource->SendRequest(
"GET",
std::move(url),
0, /*requestId*/
{}, /*headers*/
{}, /*data*/
"text", /*responseType*/
false,
0 /*timeout*/,
false /*withCredentials*/,
[](int64_t) {});
//clang-format on

rcPromise.get_future().wait();

Assert::AreEqual({}, error, L"Error encountered");
Assert::AreEqual(static_cast<int64_t>(200), response.StatusCode);
}

TEST_METHOD(RequestGetMissingUserAgentFails) {
// string url = "http://localhost:" + std::to_string(s_port);
string url = "https://api.github.com/repos/microsoft/react-native-macos";

promise<void> rcPromise;
string error;
IHttpResource::Response response;

auto resource = IHttpResource::Make();
resource->SetOnResponse([&rcPromise, &response](int64_t, IHttpResource::Response callbackResponse) {
response = callbackResponse;
rcPromise.set_value();
});
resource->SetOnError([&rcPromise, &error](int64_t, string &&message, bool) {
error = std::move(message);
rcPromise.set_value();
});

//clang-format off
resource->SendRequest(
"GET",
std::move(url),
0, /*requestId*/
{}, /*headers*/
{}, /*data*/
"text", /*responseType*/
false,
0 /*timeout*/,
false /*withCredentials*/,
[](int64_t) {});
//clang-format on

rcPromise.get_future().wait();

Assert::AreEqual({}, error, L"Error encountered");
Assert::AreEqual(static_cast<int64_t>(403), response.StatusCode);
}

TEST_METHOD(RequestGetFails) {
string error;
promise<void> promise;
Expand Down
2 changes: 0 additions & 2 deletions vnext/Shared/Networking/OriginPolicyHttpFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,8 +640,6 @@ void OriginPolicyHttpFilter::ValidateResponse(HttpResponseMessage const &respons
}

ResponseOperation OriginPolicyHttpFilter::SendPreflightAsync(HttpRequestMessage const &request) const {
// TODO: Inject user agent?

auto coRequest = request;

HttpRequestMessage preflightRequest;
Expand Down
9 changes: 9 additions & 0 deletions vnext/Shared/Networking/RedirectHttpFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include "RedirectHttpFilter.h"

// React Native Windows
#include <CppRuntimeOptions.h>
#include "WinRTTypes.h"

// Windows API
Expand Down Expand Up @@ -211,6 +213,13 @@ ResponseOperation RedirectHttpFilter::SendRequestAsync(HttpRequestMessage const
method = coRequest.Method();

do {
// Set User-Agent
// See https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
auto userAgent = GetRuntimeOptionString("Http.UserAgent");
if (userAgent.size() > 0) {
coRequest.Headers().Append(L"User-Agent", winrt::to_hstring(userAgent));
}

// Send subsequent requests through the filter that doesn't have the credentials included in the first request
response =
co_await (redirectCount > 0 ? m_innerFilterWithNoCredentials : m_innerFilter).SendRequestAsync(coRequest);
Expand Down
12 changes: 12 additions & 0 deletions vnext/Shared/Shared.vcxitems.filters
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@
<ClCompile Include="$(MSBuildThisFileDirectory)Networking\RedirectHttpFilter.cpp">
<Filter>Source Files\Networking</Filter>
</ClCompile>
<ClCompile Include="$(MSBuildThisFileDirectory)Modules\BlobModule.cpp">
<Filter>Source Files\Modules</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Source Files">
Expand Down Expand Up @@ -469,6 +472,15 @@
<ClInclude Include="$(MSBuildThisFileDirectory)Networking\IRedirectEventSource.h">
<Filter>Header Files\Networking</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)Modules\BlobModule.h">
<Filter>Header Files\Modules</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)Modules\IWebSocketModuleProxy.h">
<Filter>Header Files\Modules</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)Modules\IWebSocketModuleContentHandler.h">
<Filter>Header Files\Modules</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)tracing\rnw.wprp">
Expand Down