diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h index 66ee38bc19c..05b11c90004 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h @@ -51,7 +51,7 @@ class HTTPRequest bool InvokeDelayAction(); bool WaitingDelayAction() const { return m_timeRequestComplete != -1; } #endif - void Threaded_SetComplete(CURLcode result); + void Threaded_SetComplete(CURLcode result, bool bInvokeCallback = true); // mainly used for downloads std::vector GetBuffer() { return m_vecBuffer; } @@ -97,4 +97,4 @@ class HTTPRequest std::function m_completionCallback = nullptr; std::function m_progressCallback = nullptr; -}; +}; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp index 8ac79982f8b..228c4bea571 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp @@ -91,7 +91,8 @@ void HTTPManager::Shutdown() HTTPRequest* pRequest = *it; if (pRequest != nullptr && pRequest->EasyHandleMatches(pCurlHandle)) { - pRequest->Threaded_SetComplete(m->data.result); + // During shutdown, don't invoke callbacks to avoid use-after-free + pRequest->Threaded_SetComplete(m->data.result, false); delete pRequest; m_vecRequestsInFlight.erase(it); break; @@ -226,7 +227,8 @@ void HTTPManager::Tick() #if defined(ARTIFICIAL_DELAY_HTTP_REQUESTS) pRequest->SetWaitingDelay(m->data.result); #else - pRequest->Threaded_SetComplete(m->data.result); + // During normal operation (Tick), invoke callbacks + pRequest->Threaded_SetComplete(m->data.result, true); vecItemsToRemove.push_back(pRequest); #endif } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp index ac07945109e..9f2151e5bc7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp @@ -123,7 +123,8 @@ bool HTTPRequest::InvokeDelayAction() int64_t currTime = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); if (currTime - m_timeRequestComplete > 2000) { - Threaded_SetComplete(m_pendingCURLCode); + // During normal operation (Tick), invoke callbacks + Threaded_SetComplete(m_pendingCURLCode, true); return true; } } @@ -133,7 +134,7 @@ bool HTTPRequest::InvokeDelayAction() #endif -void HTTPRequest::Threaded_SetComplete(CURLcode result) +void HTTPRequest::Threaded_SetComplete(CURLcode result, bool bInvokeCallback) { // store response code curl_easy_getinfo(m_pCURL, CURLINFO_RESPONSE_CODE, &m_responseCode); @@ -171,8 +172,17 @@ void HTTPRequest::Threaded_SetComplete(CURLcode result) NetworkLog(ELogVerbosity::LOG_RELEASE, "[%p|%s] Response was %d - %s!", this, strURIRedacted.c_str(), m_responseCode, strResponse.c_str()); - // trigger callback - InvokeCallbackIfComplete(); + // trigger callback if requested, otherwise clear it to avoid use-after-free during shutdown + if (bInvokeCallback) + { + InvokeCallbackIfComplete(); + } + else + { + // Clear the callback to prevent potential use-after-free in std::function destructor + m_completionCallback = nullptr; + m_progressCallback = nullptr; + } } void HTTPRequest::PlatformStartRequest()