mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,458 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#if defined(RBX_PLATFORM_DURANGO)
|
||||
|
||||
#include "Util/Http.h"
|
||||
|
||||
#define NOMINMAX
|
||||
// includes required for xbox
|
||||
#include <ixmlhttprequest2.h>
|
||||
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
|
||||
#include "util/HttpPlatformImpl.h" // TODO this is here for cache. Maybe move cache to separate file?
|
||||
|
||||
#include "rbx/Profiler.h"
|
||||
|
||||
DYNAMIC_FASTINT(ExternalHttpResponseTimeoutMillis)
|
||||
DYNAMIC_FASTINT(ExternalHttpResponseSizeLimitKB)
|
||||
|
||||
enum
|
||||
{
|
||||
kForceHttpAssets = 0, // force all https assets to http
|
||||
kReportHttpTimings = 0, // dprintf every url fetch time
|
||||
kReportHttpRedirects = 1, // report redirects to http
|
||||
};
|
||||
|
||||
void dprintf( const char* fmt, ... );
|
||||
|
||||
namespace boost
|
||||
{
|
||||
void intrusive_ptr_add_ref(const IUnknown* p) { const_cast<IUnknown*>(p)->AddRef(); }
|
||||
void intrusive_ptr_release(const IUnknown* p) { const_cast<IUnknown*>(p)->Release(); }
|
||||
}
|
||||
|
||||
using boost::intrusive_ptr;
|
||||
|
||||
static std::wstring s2ws( const std::string& s )
|
||||
{
|
||||
return std::wstring(s.begin(), s.end());
|
||||
}
|
||||
std::string ws2s(const wchar_t* data);
|
||||
|
||||
class Callback : public IXMLHTTPRequest2Callback
|
||||
{
|
||||
volatile long refcnt;
|
||||
const std::string url;
|
||||
std::string response;
|
||||
int status;
|
||||
HRESULT error;
|
||||
HANDLE evt;
|
||||
std::wstring redir;
|
||||
bool cacheValid;
|
||||
|
||||
enum { kWaitTimeoutMs = 120 * 1000 };
|
||||
|
||||
virtual ~Callback()
|
||||
{
|
||||
CloseHandle(evt);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// timings
|
||||
double timeStart;
|
||||
double timeRedirect; // only the last redirect matters really.
|
||||
double timeHeaders;
|
||||
double timeData;
|
||||
double timeDone;
|
||||
|
||||
|
||||
Callback( const std::string& url_ ): url(url_)
|
||||
{
|
||||
refcnt = 1;
|
||||
status = -1;
|
||||
error = S_OK;
|
||||
evt = CreateEventA(0,TRUE,0,0);
|
||||
cacheValid = false;
|
||||
|
||||
timeStart = RBX::Time::nowFastSec();
|
||||
timeRedirect = 0;
|
||||
timeHeaders = -1;
|
||||
timeData = -1;
|
||||
timeDone = -1;
|
||||
}
|
||||
|
||||
bool wait()
|
||||
{
|
||||
if ( WAIT_OBJECT_0 == WaitForSingleObject(evt, kWaitTimeoutMs) )
|
||||
return true; // ok
|
||||
|
||||
return false; // timeout or worse
|
||||
}
|
||||
|
||||
// can call only once!
|
||||
// wait() before calling!
|
||||
void getResponse( std::string& into )
|
||||
{
|
||||
return into.swap(response);
|
||||
}
|
||||
|
||||
int getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
bool getCacheValid()
|
||||
{
|
||||
return cacheValid;
|
||||
}
|
||||
|
||||
int getHresult()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
const std::wstring& getRedir() { return redir; }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// callback stuff
|
||||
|
||||
|
||||
virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppvObject) override
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = 0;
|
||||
if( riid == IID_IUnknown ) { *ppvObject = static_cast<IUnknown*>(this); return S_OK; }
|
||||
if( riid == __uuidof(IXMLHTTPRequest2Callback) ) { *ppvObject = this; return S_OK; }
|
||||
Release();
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
virtual ULONG __stdcall AddRef() override
|
||||
{
|
||||
InterlockedIncrement(&refcnt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual ULONG __stdcall Release() override
|
||||
{
|
||||
if( !InterlockedDecrement(&refcnt) ) delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual HRESULT __stdcall OnRedirect( IXMLHTTPRequest2 *pXHR, const WCHAR *pwszRedirectUrl) override
|
||||
{
|
||||
redir = pwszRedirectUrl;
|
||||
timeRedirect = RBX::Time::nowFastSec() - timeStart;
|
||||
if(kForceHttpAssets) return S_OK; // don't spam
|
||||
|
||||
if( kReportHttpRedirects && wcsncmp(pwszRedirectUrl, L"http://", 7) == 0 )
|
||||
{
|
||||
dprintf("HTTP: %s\nREDIR: %S\n", url.c_str(), pwszRedirectUrl );
|
||||
}
|
||||
|
||||
std::string redirect = ws2s(pwszRedirectUrl);
|
||||
RBX::HttpPlatformImpl::Cache::CacheResult cacheResult = RBX::HttpPlatformImpl::Cache::CacheResult::open(url.c_str(), redirect.c_str());
|
||||
|
||||
if (cacheResult.isValid())
|
||||
{
|
||||
timeData = RBX::Time::nowFastSec() - timeStart;
|
||||
response.clear();
|
||||
response = cacheResult.getResponseBody().toString();
|
||||
timeDone = timeData = RBX::Time::nowFastSec() - timeStart;
|
||||
cacheValid = true;
|
||||
|
||||
pXHR->Abort();
|
||||
SetEvent(evt);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT __stdcall OnHeadersAvailable( IXMLHTTPRequest2 *pXHR, DWORD dwStatus, const WCHAR *pwszStatus) override
|
||||
{
|
||||
status = dwStatus;
|
||||
timeHeaders = RBX::Time::nowFastSec() - timeStart;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT __stdcall OnDataAvailable( IXMLHTTPRequest2 *pXHR, ISequentialStream *pResponseStream) override
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT __stdcall OnResponseReceived( IXMLHTTPRequest2 *pXHR, ISequentialStream *pResponseStream) override
|
||||
{
|
||||
enum { kChunkSize = 32767 };
|
||||
unsigned long long int bytes = 0; // yeah yeah
|
||||
HRESULT hr;
|
||||
ULONG rd;
|
||||
|
||||
timeData = RBX::Time::nowFastSec() - timeStart;
|
||||
|
||||
response.clear();
|
||||
|
||||
do{
|
||||
response.insert( response.end(), kChunkSize, '\0' );
|
||||
hr = pResponseStream->Read( bytes + (char*)response.data(), kChunkSize, &rd );
|
||||
if( FAILED(hr) )
|
||||
return E_FAIL;
|
||||
bytes += rd;
|
||||
}
|
||||
while(rd == kChunkSize );
|
||||
response.resize( bytes );
|
||||
|
||||
timeDone = timeData = RBX::Time::nowFastSec() - timeStart;
|
||||
|
||||
SetEvent(evt);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT __stdcall OnError( IXMLHTTPRequest2 *pXHR, HRESULT hrError) override
|
||||
{
|
||||
error = hrError;
|
||||
SetEvent(evt);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
class Stream : public ISequentialStream
|
||||
{
|
||||
volatile long refcnt;
|
||||
std::istream& stream;
|
||||
|
||||
virtual ~Stream(){}
|
||||
|
||||
public:
|
||||
Stream( std::istream& s ): stream(s), refcnt(1) {}
|
||||
|
||||
virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppvObject) override
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = 0;
|
||||
if( riid == IID_IUnknown ) { *ppvObject = static_cast<IUnknown*>(this); return S_OK; }
|
||||
if( riid == IID_ISequentialStream ) { *ppvObject = this; return S_OK; }
|
||||
Release();
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
virtual ULONG __stdcall AddRef() override
|
||||
{
|
||||
InterlockedIncrement(&refcnt); return 0;
|
||||
}
|
||||
|
||||
virtual ULONG __stdcall Release() override
|
||||
{
|
||||
if( !InterlockedDecrement(&refcnt) ) delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual HRESULT __stdcall Read( void *pv, ULONG cb, ULONG *pcbRead ) override
|
||||
{
|
||||
stream.read((char*)pv, cb);
|
||||
*pcbRead = stream.gcount();
|
||||
return *pcbRead == cb ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
virtual HRESULT __stdcall Write( const void *pv, ULONG cb, ULONG *pcbWritten ) override
|
||||
{
|
||||
RBXASSERT(false);
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
};
|
||||
|
||||
static void addHeader( IXMLHTTPRequest2* request, const std::wstring& header, const std::wstring value, const std::string& theUrl )
|
||||
{
|
||||
HRESULT hr = request->SetRequestHeader( header.c_str(), value.c_str() );
|
||||
if( FAILED(hr) )
|
||||
throw RBX::runtime_error( "http failed to add header '%S' : '%S' (0x%x) [%s]", header.c_str(), value.c_str(), hr, theUrl.c_str());
|
||||
}
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
void Http::httpGetPostXbox(bool isPost, std::istream& data, const std::string& contentType, bool compressData, const HttpAux::AdditionalHeaders& additionalHeaders, bool externalRequest, HttpCache::Policy cachePolicy, std::string& response)
|
||||
{
|
||||
std::string theUrl = alternateUrl.empty() ? url : alternateUrl;
|
||||
HRESULT hr;
|
||||
intrusive_ptr< Callback > callback;
|
||||
intrusive_ptr< IXMLHTTPRequest2 > request;
|
||||
|
||||
RBXPROFILER_SCOPE("Http", __FUNCTION__);
|
||||
RBXPROFILER_LABEL("Http", theUrl.c_str());
|
||||
|
||||
RBX::Timer<RBX::Time::Precise> timer;
|
||||
|
||||
static const std::wstring userAgentHeader = L"User-Agent";
|
||||
static const std::wstring userAgentValue = s2ws(Http::rbxUserAgent) + L" ROBLOX Xbox App 1.0.0";
|
||||
static const std::wstring accessKeyHeader = L"XboxAccessKey";
|
||||
static const std::wstring accessKeyValue = L"b9309a51-7b54-458c-ad73-7b2c287497e2";
|
||||
static const std::wstring contentTypeHeader = L"Content-Type";
|
||||
static const std::wstring csrfTokenHeader = L"X-CSRF-TOKEN";
|
||||
|
||||
|
||||
if( kForceHttpAssets && theUrl.find("https://") != std::string::npos && theUrl.find("/asset") != std::string::npos )
|
||||
theUrl.replace(0,5, "http");
|
||||
|
||||
if (theUrl.size()==0)
|
||||
throw RBX::runtime_error("http empty url");
|
||||
|
||||
if (!Http::trustCheck(theUrl.c_str(), externalRequest))
|
||||
throw RBX::runtime_error("http trust check failed for %s", theUrl.c_str());
|
||||
|
||||
if(0) dprintf("http %s %s\n", (isPost?"POST":"GET"), theUrl.c_str());
|
||||
|
||||
hr = ::CoCreateInstance( __uuidof(FreeThreadedXMLHTTP60), 0, CLSCTX_SERVER, __uuidof(IXMLHTTPRequest2), (void**)&request );
|
||||
if(FAILED(hr) || !request)
|
||||
throw RBX::runtime_error("http is not available at the moment (0x%x) [%s]", hr, theUrl.c_str());
|
||||
|
||||
callback = boost::intrusive_ptr< Callback >( new Callback(theUrl), false );
|
||||
|
||||
hr = request->Open( (isPost ? L"POST" : L"GET"), s2ws(theUrl).c_str(), callback.get(), 0, 0, 0, 0 );
|
||||
if (FAILED(hr))
|
||||
throw RBX::runtime_error("http could not open connection (0x%x) [%s]", hr, theUrl.c_str());
|
||||
|
||||
addHeader( request.get(), userAgentHeader, userAgentValue, theUrl );
|
||||
|
||||
if( !externalRequest )
|
||||
addHeader( request.get(), accessKeyHeader, accessKeyValue, theUrl );
|
||||
|
||||
for( auto& x : additionalHeaders )
|
||||
{
|
||||
addHeader( request.get(), s2ws(x.first), s2ws(x.second), theUrl );
|
||||
}
|
||||
|
||||
if( isPost )
|
||||
{
|
||||
addHeader( request.get(), contentTypeHeader, s2ws(contentType), theUrl );
|
||||
if (!externalRequest)
|
||||
{
|
||||
addHeader( request.get(), csrfTokenHeader, s2ws(getLastCsrfToken()), theUrl );
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef RBX_XBOX_SITETEST1
|
||||
// fun with snicker doodles (sitetest1 auth)
|
||||
__int64 sysTime;
|
||||
GetSystemTimeAsFileTime( (FILETIME*)&sysTime);
|
||||
sysTime += (__int64)10000000 * 3600 * 24 * 365 * 100;
|
||||
|
||||
XHR_COOKIE site1Cookie = {};
|
||||
site1Cookie.pwszUrl = L".sitetest1.pizzaboxer.fun/";
|
||||
site1Cookie.pwszName = L"SnickerdoodleConstraint";
|
||||
site1Cookie.pwszValue = L"";
|
||||
site1Cookie.ftExpires = (FILETIME&)sysTime;
|
||||
site1Cookie.pwszP3PPolicy = L"";
|
||||
site1Cookie.dwFlags = XHR_COOKIE_IS_SESSION;
|
||||
|
||||
DWORD state = 0;
|
||||
hr = request->SetCookie(&site1Cookie, &state);
|
||||
#endif
|
||||
|
||||
request->SetProperty(XHR_PROP_ONDATA_THRESHOLD, XHR_PROP_ONDATA_NEVER);
|
||||
|
||||
// send the request
|
||||
if( !isPost )
|
||||
{
|
||||
// GET
|
||||
hr = request->Send( 0, 0 );
|
||||
if( FAILED(hr) )
|
||||
throw RBX::runtime_error("http failed to send (0x%x) [%s]", hr, theUrl.c_str() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// POST
|
||||
data.clear(); // this does not clear the stream!
|
||||
const unsigned long long int size = data.seekg(0, std::ios_base::end).tellg();
|
||||
data.seekg(0);
|
||||
|
||||
hr = request->Send( new Stream(data), size );
|
||||
if( FAILED(hr) )
|
||||
RBX::runtime_error("http send failed (0x%x) [%s]", hr, theUrl.c_str() );
|
||||
}
|
||||
|
||||
// get the results
|
||||
int statusCode = -1;
|
||||
|
||||
if( !callback->wait() )
|
||||
{
|
||||
// timeout
|
||||
request->Abort();
|
||||
throw RBX::runtime_error( "http TIMEOUT [%s]", theUrl.c_str() );
|
||||
}
|
||||
|
||||
hr = callback->getHresult();
|
||||
if (FAILED(hr))
|
||||
throw RBX::runtime_error( "http error hr=0x%x [%s]", hr, theUrl.c_str() );
|
||||
|
||||
statusCode = callback->getStatus();
|
||||
callback->getResponse(response);
|
||||
|
||||
if( statusCode == 200 || statusCode == 201 || callback->getCacheValid() )
|
||||
{
|
||||
if (!isPost && cachePolicy == HttpCache::Policy::PolicyFinalRedirect && !callback->getCacheValid())
|
||||
{
|
||||
wchar_t* requestHeaders;
|
||||
request->GetAllResponseHeaders(&requestHeaders);
|
||||
std::string headersStr = ws2s(requestHeaders);
|
||||
CoTaskMemFree(requestHeaders);
|
||||
|
||||
HttpPlatformImpl::Cache::Data headers(headersStr.data(), headersStr.size());
|
||||
HttpPlatformImpl::Cache::Data body(response.data(), response.size());
|
||||
|
||||
std::string redirUrl = ws2s(callback->getRedir().data());
|
||||
|
||||
try
|
||||
{
|
||||
HttpPlatformImpl::Cache::CacheResult::update(url.c_str(), redirUrl.c_str(), statusCode, headers, body);
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
dprintf("Cache update failed: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
if( kReportHttpTimings )
|
||||
{
|
||||
const wchar_t* redir = callback->getRedir().c_str();
|
||||
dprintf( "HTTP %s %d %.0f ms (%.0f ms / %.0f ms / %.0f ms) : %s%s%S\n", (isPost?"POST":"GET"), statusCode, 1000*callback->timeDone,
|
||||
1000*callback->timeRedirect, 1000*callback->timeHeaders, 1000*callback->timeData,
|
||||
theUrl.c_str(), (*redir?" ----> ":""), redir);
|
||||
}
|
||||
return; // success!
|
||||
}
|
||||
|
||||
// error handling and stuff
|
||||
//special case: CSRF token. Important security stuff. Talk to our web team for more info. ("Hey, what's CSRF?")
|
||||
if( statusCode == 403 && isPost && !externalRequest)
|
||||
{
|
||||
WCHAR* header = 0;
|
||||
HRESULT hr = request->GetResponseHeader(L"X-CSRF-TOKEN",&header);
|
||||
if( FAILED(hr) )
|
||||
throw http_status_error(statusCode, theUrl);
|
||||
|
||||
setLastCsrfToken(std::string(header, header + wcslen(header)));
|
||||
CoTaskMemFree(header);
|
||||
response.clear();
|
||||
return httpGetPostXbox( isPost, data, contentType, compressData, additionalHeaders, externalRequest, cachePolicy, response);
|
||||
}
|
||||
throw http_status_error( statusCode, theUrl );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// stubs for studio-related things
|
||||
namespace RBX{ namespace HttpPlatformImpl {
|
||||
|
||||
void init(Http::CookieSharingPolicy cookieSharingPolicy) { RBXASSERT(0); }
|
||||
void setCookiesForDomain(const std::string& domain, const std::string& cookies) { RBXASSERT(0); }
|
||||
void getCookiesForDomain(const std::string& domain, std::string& cookies) { RBXASSERT(0); }
|
||||
boost::filesystem::path getRobloxCookieJarPath() { RBXASSERT(0); return ""; }
|
||||
void setProxy(const std::string& host, long port) { RBXASSERT(0); }
|
||||
void perform(HttpOptions& options, std::string& response) { RBXASSERT(0); }
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user