Skip to content

Commit 34abe2c

Browse files
datah4zardGent Semajjasonsandlin
authored
Fix infinite recursion on HTTP filters (#1023)
* Fix infinite recursion on HTTP filters * PR feedback --------- Co-authored-by: Gent Semaj <gentsemaj@xbox.com> Co-authored-by: Jason Sandlin <jasonsa@microsoft.com>
1 parent 4c862e9 commit 34abe2c

4 files changed

Lines changed: 79 additions & 2 deletions

File tree

‎Source/Global/NetworkState.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ IHttpProvider & NetworkState::HttpProvider() noexcept
7575
{
7676
return externalProvider;
7777
}
78+
79+
return PlatformHttpProvider();
80+
}
81+
82+
IHttpProvider & NetworkState::PlatformHttpProvider() noexcept
83+
{
7884
assert(m_httpProvider);
7985
return *m_httpProvider;
8086
}

‎Source/Global/NetworkState.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class NetworkState
4141
public: // Http
4242
IHttpProvider& HttpProvider() noexcept;
4343

44+
// This will always return the platform's built-in provider, ignoring any external provider.
45+
IHttpProvider& PlatformHttpProvider() noexcept;
46+
4447
Result<UniquePtr<HC_CALL>> HttpCallCreate() noexcept;
4548

4649
HRESULT HttpCallPerformAsync(

‎Source/Global/global_publics.cpp‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,15 @@ void CALLBACK HttpProviderPerformAsyncProxy(
147147
if (nullptr == httpSingleton)
148148
{
149149
XAsyncComplete(asyncBlock, E_HC_NOT_INITIALISED, 0);
150+
return;
150151
}
151-
else
152+
153+
// NetworkState::HttpProvider() returns the client's own callback once one is installed;
154+
// using that here would then recurse infinitely.
155+
HRESULT hr = httpSingleton->m_networkState->PlatformHttpProvider().PerformAsync(call, asyncBlock);
156+
if (FAILED(hr))
152157
{
153-
httpSingleton->m_networkState->HttpProvider().PerformAsync(call, asyncBlock);
158+
XAsyncComplete(asyncBlock, hr, 0);
154159
}
155160
}
156161

‎Tests/UnitTests/Tests/HttpTests.cpp‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "DefineTestMacros.h"
88
#include "Utils.h"
99
#include "../global/global.h"
10+
#include "Platform/ExternalHttpProvider.h"
1011
#include <httpClient/httpProvider.h>
1112

1213
#if HC_PLATFORM == HC_PLATFORM_GDK
@@ -70,6 +71,30 @@ static void CALLBACK PerformCallback(
7071
XAsyncComplete(asyncBlock, S_OK, 0);
7172
}
7273

74+
static HCCallPerformFunction g_defaultPerform = nullptr;
75+
static void* g_defaultPerformContext = nullptr;
76+
static std::atomic<int> g_chainingFilterCalls{ 0 };
77+
78+
// Models the filter documented on HCGetHttpCallPerformFunction: chain to the default
79+
// implementation after inspecting the call.
80+
static void CALLBACK ChainingPerformCallback(
81+
_In_ HCCallHandle call,
82+
_Inout_ XAsyncBlock* asyncBlock,
83+
_In_opt_ void* ctx,
84+
_In_opt_ HCPerformEnv env
85+
)
86+
{
87+
UNREFERENCED_PARAMETER(ctx);
88+
89+
if (++g_chainingFilterCalls > 1)
90+
{
91+
// Re-entered
92+
XAsyncComplete(asyncBlock, E_UNEXPECTED, 0);
93+
return;
94+
}
95+
96+
g_defaultPerform(call, asyncBlock, g_defaultPerformContext, env);
97+
}
7398

7499
DEFINE_TEST_CLASS(HttpTests)
75100
{
@@ -128,6 +153,44 @@ DEFINE_TEST_CLASS(HttpTests)
128153
VERIFY_IS_NULL(get_http_singleton());
129154
}
130155

156+
DEFINE_TEST_CASE(TestPerformCallbackChainsToDefaultProvider)
157+
{
158+
DEFINE_TEST_CASE_PROPERTIES(TestPerformCallbackChainsToDefaultProvider);
159+
160+
// This test only works while no client callback is installed.
161+
VERIFY_IS_FALSE(ExternalHttpProvider::Get().HasCallback());
162+
163+
g_chainingFilterCalls = 0;
164+
VERIFY_ARE_EQUAL(S_OK, HCGetHttpCallPerformFunction(&g_defaultPerform, &g_defaultPerformContext));
165+
VERIFY_IS_NOT_NULL(g_defaultPerform);
166+
167+
VERIFY_ARE_EQUAL(S_OK, HCSetHttpCallPerformFunction(&ChainingPerformCallback, nullptr));
168+
VERIFY_ARE_EQUAL(S_OK, HCInitialize(nullptr));
169+
170+
HCCallHandle call{ nullptr };
171+
VERIFY_ARE_EQUAL(S_OK, HCHttpCallCreate(&call));
172+
VERIFY_ARE_EQUAL(S_OK, HCHttpCallRequestSetUrl(call, "GET", "http://127.0.0.1:1/"));
173+
VERIFY_ARE_EQUAL(S_OK, HCHttpCallRequestSetRetryAllowed(call, false));
174+
175+
XTaskQueueHandle queue{ nullptr };
176+
VERIFY_ARE_EQUAL(S_OK, XTaskQueueCreate(
177+
XTaskQueueDispatchMode::ThreadPool,
178+
XTaskQueueDispatchMode::ThreadPool,
179+
&queue));
180+
181+
XAsyncBlock asyncBlock{};
182+
asyncBlock.queue = queue;
183+
VERIFY_ARE_EQUAL(S_OK, HCHttpCallPerformAsync(call, &asyncBlock));
184+
XAsyncGetStatus(&asyncBlock, true);
185+
186+
VERIFY_ARE_EQUAL(S_OK, HCHttpCallCloseHandle(call));
187+
XTaskQueueCloseHandle(queue);
188+
HCCleanup();
189+
190+
// Has to happen after HCCleanup() or else other tests break.
191+
VERIFY_ARE_EQUAL(1, g_chainingFilterCalls.load());
192+
}
193+
131194
DEFINE_TEST_CASE(TestPerformCallback)
132195
{
133196
DEFINE_TEST_CASE_PROPERTIES_FOCUS(TestPerformCallback);

0 commit comments

Comments
 (0)