#include "SCENE.H" #ifdef ONLINE_MODE //#pragma optimize("",off) //#include "stdafx.h" #define CURL_STATICLIB 1 #include #include #include // requires recent Windows SDK, tested with v6.1 #include #include "http_client_curl.h" #pragma comment(lib, "winhttp.lib") #define _Curl (CURL *)_CurlStruct #ifdef USE_OPENSSL #include "openssl/crypto.h" #include struct CRYPTO_dynlock_value { MUTEX_TYPE mutex; }; static MUTEX_TYPE *mutex_buf; static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line) { struct CRYPTO_dynlock_value *value; value = (struct CRYPTO_dynlock_value*)malloc(sizeof(struct CRYPTO_dynlock_value)); if (value) MUTEX_SETUP(value->mutex); return value; } static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) { if (mode & CRYPTO_LOCK) MUTEX_LOCK(l->mutex); else MUTEX_UNLOCK(l->mutex); } static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line) { MUTEX_CLEANUP(l->mutex); free(l); } void locking_function(int mode, int n, const char *file, int line) { if (mode & CRYPTO_LOCK) MUTEX_LOCK(mutex_buf[n]); else MUTEX_UNLOCK(mutex_buf[n]); } unsigned long id_function() { return (unsigned long)THREAD_ID; } bool CRYPTO_thread_setup() { mutex_buf = (MUTEX_TYPE*)malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE)); if (!mutex_buf) return false; for (int i = 0; i < CRYPTO_num_locks(); i++) MUTEX_SETUP(mutex_buf[i]); CRYPTO_set_id_callback(id_function); CRYPTO_set_locking_callback(locking_function); CRYPTO_set_dynlock_create_callback(dyn_create_function); CRYPTO_set_dynlock_lock_callback(dyn_lock_function); CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); return true; } void CRYPTO_thread_cleanup() { int i; if (!mutex_buf) return; CRYPTO_set_id_callback(NULL); CRYPTO_set_locking_callback(NULL); CRYPTO_set_dynlock_create_callback(NULL); CRYPTO_set_dynlock_lock_callback(NULL); CRYPTO_set_dynlock_destroy_callback(NULL); for (i = 0; i < CRYPTO_num_locks(); i++) MUTEX_CLEANUP(mutex_buf[i]); free(mutex_buf); mutex_buf = NULL; } #endif //USE_OPENSSL // From CamCam utilities void SaveDBGInfo(char *text); #define _CRT_SECURE_NO_WARNINGS #define VARGS_TO_STR(_str, _format, _size) \ char _strbuf[_size]; \ va_list _args; \ va_start(_args, _format); \ int _res = _vsnprintf(_strbuf, _size-1, _format, _args); \ if ((_res == -1) || (_res == _size-1)) \ { \ _strbuf[_size-1] = '\0'; \ } \ va_end(_args); \ _str = _strbuf void logStr(const char *format, ...) { char *str; const int maxLineSize = 512; VARGS_TO_STR(str, format, maxLineSize); if (strlen(str)getCurlStruct()), src, 0); else _Str = curl_easy_unescape((CURL*)(client->getCurlStruct()), src, 0, NULL); } UEString::~UEString() { curl_free((void*)_Str); } #undef _CRT_SECURE_NO_WARNINGS // CURL callbacks size_t onDataReceivedCallback(void *buffer, size_t size, size_t nmemb, void *pHttpClient) { CHttpClient * httpClient = static_cast(pHttpClient); httpClient->onDataReceived((char*)buffer, size*nmemb); //ifPrepareLuaFunction(GameMessageReceive) //{ // callPreparedLuaCallback(GameMessageReceive, 0); //} return size*nmemb; } size_t onHeaderReceivedCallback( void *buffer, size_t size, size_t nmemb, void *pHttpClient) { CHttpClient * httpClient = static_cast(pHttpClient); httpClient->onHeaderReceived((char*)buffer, size*nmemb); return size*nmemb; } int onProgressCallback(void *pHttpClient, double dltotal, double dlnow, double ultotal, double ulnow) { CHttpClient * httpClient = static_cast(pHttpClient); return httpClient->onProgress(dltotal, dlnow, ultotal, ulnow); } int CHttpClient::_InstanceCount = 0; MUTEX_TYPE CHttpClient::_MutexInstanceCount = GET_MUTEX_SETUP; // *************************************************************************** bool CHttpClient::init() { if (_Initialized) return true; ctassert(MY_CURL_ERROR_SIZE == CURL_ERROR_SIZE); ctassert(ErrorInvalidFile == CURLE_READ_ERROR); // Global init MUTEX_LOCK(_MutexInstanceCount); if (_InstanceCount==0) { #ifdef USE_OPENSSL if (!CRYPTO_thread_setup()) logStr("WRN: Could not setup crypto locking functions"); #endif curl_global_init(CURL_GLOBAL_ALL); } ++_InstanceCount; MUTEX_UNLOCK(_MutexInstanceCount); curl_version_info_data *vdata = curl_version_info(CURLVERSION_NOW); //logStr("Libcurl v%s", vdata->version); if ((vdata->features & CURL_VERSION_SSL) == 0) logStr("WRN: SSL not supported"); _Curl = curl_easy_init(); if(_Curl == NULL) return false; _CookiesList.clear(); _Initialized = true; return true; } // *************************************************************************** void CHttpClient::setProxy(const char *ip_port) { curl_easy_setopt(_Curl, CURLOPT_PROXY, ip_port); } // *************************************************************************** void CHttpClient::setUserAgent(const char *userAgent) { _UserAgentStr.clear(); _UserAgentStr.appendStrCat(userAgent, MaxUserAgentChars); } // *************************************************************************** bool CHttpClient::setCredentials(const char *user, const char *password) { _AuthStr.clear(); if ((user == NULL) || (user[0] == '\0')) return true; _AuthStr.appendStrCat(user, MaxUserChars); _AuthStr.appendStrCat(":", 1); _AuthStr.appendStrCat(password, MaxPasswordChars); return true; } // *************************************************************************** bool CHttpClient::setCredentials(const char *userPassword) { _AuthStr.clear(); if ((userPassword == NULL) || (userPassword[0] == '\0')) return true; _AuthStr.appendStrCat(userPassword, MaxUserChars+1+MaxPasswordChars); return true; } // *************************************************************************** bool CHttpClient::setCookie(const char *cookieName, const char *cookieValue) { //_CookieStr.clear(); if ((cookieName == NULL) || (cookieName[0] == '\0')) return true; if(_CookiesList.find(std::string(cookieName)) != _CookiesList.end()) { _CookiesList.erase(_CookiesList.find(std::string(cookieName))); } _CookiesList.insert( std::make_pair (std::string(cookieName), std::string(cookieValue))); //_CookieStr.appendStrCat(cookieName, MaxCookieNameChars); //_CookieStr.appendStrCat("=", 1); //_CookieStr.appendStrCat(cookieValue, MaxCookieValueChars); return true; } // *************************************************************************** void CHttpClient::resetCookie() { _CookieStr.clear(); _CookiesList.clear(); } //const char *CAFilename = "./ssl_ca_cert.pem"; // *************************************************************************** bool CHttpClient::setVerifyServer(bool verify, const char *certicatesFilename) { curl_easy_setopt(_Curl, CURLOPT_SSL_VERIFYHOST, verify ? 2 : 0); // set to compare the actual server hostname with the hostname specified in the certificate coming from the server curl_easy_setopt(_Curl, CURLOPT_SSL_VERIFYPEER, verify ? 1 : 0); // set to check that the certificate coming from the server is valid (needs local certificate to check against) //curl_easy_setopt(_Curl, CURLOPT_SSL_CTX_FUNCTION, *sslctx_function); // would allow to provide the CA in memory instead of using CURLOPT_CAINFO, but needs to include and link OpenSSL curl_easy_setopt(_Curl, CURLOPT_CAINFO, certicatesFilename); // only needed if the default OpenSSL CA certificates are not sufficient to validate the server certificate //curl_easy_setopt(_Curl, CURLOPT_CAPATH, NULL); //curl_easy_setopt(_Curl, CURLOPT_SSLCERTTYPE, "PEM"); return true; } // *************************************************************************** bool CHttpClient::sendRequest(const char *pageURL, const char *paramStr, bool isPostRequest, bool wantHeader, bool verbose) { _Headers.clear(); _ReceiveBuffer.clear(); //HttpPage().swap(_ReceiveBuffer); if (verbose) curl_easy_setopt(_Curl, CURLOPT_VERBOSE, 1); if (!_UserAgentStr.empty()) { curl_easy_setopt(_Curl, CURLOPT_USERAGENT, _UserAgentStr.c_str()); } // Authentication if (!_AuthStr.empty()) { curl_easy_setopt(_Curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_easy_setopt(_Curl, CURLOPT_USERPWD, _AuthStr.c_str()); curl_easy_setopt(_Curl, CURLOPT_UNRESTRICTED_AUTH, 1); } if ((paramStr == NULL) || (paramStr[0] == '\0')) { // Just set URL curl_easy_setopt(_Curl, CURLOPT_URL, pageURL); curl_easy_setopt(_Curl, CURLOPT_POST, false); } else { if (isPostRequest) { // Set URL and post params curl_easy_setopt(_Curl, CURLOPT_URL, pageURL); curl_easy_setopt(_Curl, CURLOPT_POST, true); curl_easy_setopt(_Curl, CURLOPT_POSTFIELDS, paramStr); } else { // Append params to URL SimpleString urlWithParams; urlWithParams.appendStrCat(pageURL, MaxPageURLChars); urlWithParams.appendStrCat("?", 1); urlWithParams.appendStrCat(paramStr, MaxURLParamChars); curl_easy_setopt(_Curl, CURLOPT_HTTPGET, true); curl_easy_setopt(_Curl, CURLOPT_URL, urlWithParams.c_str()); } } // Cookie if (!_CookiesList.empty()) { // curl_easy_setopt(_Curl, CURLOPT_COOKIEFILE, "C:\c.txt");//_CookieStr.c_str()); // Make Cookies std::string listC(""); for (CookiesList::iterator it = _CookiesList.begin(); it != _CookiesList.end(); ++ it) { listC = listC + it->first + std::string("=") + it->second + std::string(";"); } //logStr("Cookies CURL=%s", listC.c_str()); curl_easy_setopt(_Curl, CURLOPT_COOKIE, listC.c_str()); } if (wantHeader) { // Include the header in the response curl_easy_setopt(_Curl, CURLOPT_HEADER, 1); } // Register the receive callback curl_easy_setopt(_Curl, CURLOPT_WRITEFUNCTION, onDataReceivedCallback); curl_easy_setopt(_Curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(_Curl, CURLOPT_HEADERFUNCTION, onHeaderReceivedCallback); curl_easy_setopt(_Curl, CURLOPT_WRITEHEADER, this); // Register the progress callback (this make the requests interruptable) curl_easy_setopt(_Curl, CURLOPT_NOPROGRESS, FALSE); curl_easy_setopt(_Curl, CURLOPT_PROGRESSFUNCTION, onProgressCallback); curl_easy_setopt(_Curl, CURLOPT_PROGRESSDATA, this); curl_easy_setopt(_Curl, CURLOPT_ERRORBUFFER, _CurlErrorString.Buf); // Redirection options if(_WantRedirection) { curl_easy_setopt(_Curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(_Curl, CURLOPT_MAXREDIRS, _MaxRedirection); } else { curl_easy_setopt(_Curl, CURLOPT_FOLLOWLOCATION, 0); } curl_easy_setopt(_Curl, CURLOPT_POST301, 1); // aka CURLOPT_POSTREDIR in newer versions of Curl curl_easy_setopt(_Curl, CURLOPT_UNRESTRICTED_AUTH, 1); curl_easy_setopt(_Curl, CURLOPT_AUTOREFERER, 1); // Send _RequestCode = curl_easy_perform(_Curl); if (_RequestCode != 0) { if (verbose) logStr("WRN: %s", _CurlErrorString.c_str()); return false; } // Get result curl_easy_getinfo(_Curl, CURLINFO_RESPONSE_CODE, &_RequestCode); if (verbose) logStr("Response Code: %u", _RequestCode); return true; } // *************************************************************************** bool CHttpClient::addMultipartParam(const char *field, const MultipartParam& value) { switch (value.Source) { case CHttpClient::ParamCString: return (curl_formadd((curl_httppost**)&_FirstParamStruct, (curl_httppost**)&_LastParamStruct, CURLFORM_COPYNAME, field, CURLFORM_COPYCONTENTS, value.Str, CURLFORM_END) == 0); //break; case CHttpClient::ParamLoadFile: return (curl_formadd((curl_httppost**)&_FirstParamStruct, (curl_httppost**)&_LastParamStruct, CURLFORM_COPYNAME, field, CURLFORM_FILE, value.Str, CURLFORM_END) == 0); //break; default:; } return false; } // *************************************************************************** bool CHttpClient::sendMultipartRequest(const char *pageURL, bool wantHeader, bool verbose) { _Headers.clear(); _ReceiveBuffer.clear(); if (verbose) curl_easy_setopt(_Curl, CURLOPT_VERBOSE, 1); if (!_UserAgentStr.empty()) { curl_easy_setopt(_Curl, CURLOPT_USERAGENT, _UserAgentStr.c_str()); } // Authentication if (!_AuthStr.empty()) { curl_easy_setopt(_Curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_easy_setopt(_Curl, CURLOPT_USERPWD, _AuthStr.c_str()); } // URL and params curl_easy_setopt(_Curl, CURLOPT_URL, pageURL); curl_easy_setopt(_Curl, CURLOPT_HTTPPOST, (curl_httppost *)_FirstParamStruct); // Cookie if (!_CookieStr.empty()) curl_easy_setopt(_Curl, CURLOPT_COOKIE, _CookieStr.c_str()); if (wantHeader) { // Include the header in the response curl_easy_setopt(_Curl, CURLOPT_HEADER, 1); } // Register the receive callback curl_easy_setopt(_Curl, CURLOPT_WRITEFUNCTION, onDataReceivedCallback); curl_easy_setopt(_Curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(_Curl, CURLOPT_HEADERFUNCTION, onHeaderReceivedCallback); curl_easy_setopt(_Curl, CURLOPT_WRITEHEADER, this); // Register the progress callback (this make the requests interruptable) curl_easy_setopt(_Curl, CURLOPT_NOPROGRESS, FALSE); curl_easy_setopt(_Curl, CURLOPT_PROGRESSFUNCTION, onProgressCallback); curl_easy_setopt(_Curl, CURLOPT_PROGRESSDATA, this); ctassert(MY_CURL_ERROR_SIZE == CURL_ERROR_SIZE); curl_easy_setopt(_Curl, CURLOPT_ERRORBUFFER, _CurlErrorString.Buf); // Send _RequestCode = curl_easy_perform(_Curl); curl_formfree((curl_httppost *)_FirstParamStruct); _FirstParamStruct = NULL; _LastParamStruct = NULL; if (_RequestCode != 0) { if (verbose) logStr("WRN: %s", _CurlErrorString.c_str()); return false; } // Get result curl_easy_getinfo(_Curl, CURLINFO_RESPONSE_CODE, &_RequestCode); if (verbose) logStr("Response Code: %u", _RequestCode); return true; } // *************************************************************************** void CHttpClient::onDataReceived(char * buffer, size_t size) { #ifdef DYNALLOC_HTTPCLIENT _ReceiveBuffer.insert(_ReceiveBuffer.end(), buffer, buffer+size); #else _ReceiveBuffer.appendBinary(buffer, size); #endif } // *************************************************************************** void CHttpClient::onHeaderReceived(char * buffer, size_t size) { // Parse and store into map std::string s(buffer, buffer+size); std::string::size_type p = s.find(":"); if (p != std::string::npos) { // Ex: "Content-Type" -> "text/html" std::string::size_type q = p+1; while (q "" _Headers[s.substr(0, s.rfind("\r\n"))] = std::string(); } } // *************************************************************************** int CHttpClient::onProgress(double dltotal, double dlnow, double ultotal, double ulnow) { #ifdef SIMULATE_SLOW_UPLOAD if (ultotal != 0) Sleep(1000); #endif _DLTotal = dltotal; _DLNow = dlnow; _ULTotal = ultotal; _ULNow = ulnow; return (isAborting() ? 1 : 0); } // *************************************************************************** #ifndef USER_MODE unsigned int CHttpClient::getMemoryUsage() const { MUTEX_LOCK(_MutexInstanceCount); unsigned int size = 0; size += sizeof(*this); size += sizeof(char) * (MaxUserAgentChars + MaxAuthChars + MaxCookieChars + MY_CURL_ERROR_SIZE + 1); // sizeof _Headers for(HeaderMap::const_iterator it = _Headers.begin(); it != _Headers.end(); ++ it) { size += sizeof(std::string::value_type) * (it->first.capacity() + it->second.capacity()); } size += _ReceiveBuffer.capacity() * sizeof(std::string::value_type); MUTEX_UNLOCK(_MutexInstanceCount); return size; } #endif // *************************************************************************** const HttpPage& CHttpClient::getResponsePage(bool verbose) const { if (verbose) logStr("Accessing %u bytes", _ReceiveBuffer.size()); return _ReceiveBuffer; } // *************************************************************************** HttpPage& CHttpClient::getAndEditResponsePage(bool verbose) { if (verbose) logStr("Accessing/editing %u bytes", _ReceiveBuffer.size()); return _ReceiveBuffer; } // *************************************************************************** void BackgroundTaskRun(void *instance) { (static_cast(instance))->run(); } // *************************************************************************** bool CBackgroundTask::start() { _IsRunning = true; _beginthread(BackgroundTaskRun, 0, this); return true; } // *************************************************************************** void CBackgroundTask::waitForExit() { while (_IsRunning) { Sleep(10); } } // *************************************************************************** void CHttpClient::release() { curl_easy_cleanup(_Curl); _Initialized = false; // Global cleanup MUTEX_LOCK(_MutexInstanceCount); --_InstanceCount; if (_InstanceCount == 0) { curl_global_cleanup(); #ifdef USE_OPENSSL CRYPTO_thread_cleanup(); #endif } MUTEX_UNLOCK(_MutexInstanceCount); } // *************************************************************************** CHttpClient::~CHttpClient() { if (_Initialized) release(); } // *************************************************************************** bool CHttpClient::nextTryAutoConfigureProxy(const char *url, void* hSession) { // Windows-specific code (needs XP SP1 or newer) // If previous calls could not deduce the proxy, abandon if (_CurrentPacm == PacmFailed) { logStr(__FUNCTION__"-error: could not determine proxy"); if (hSession) WinHttpCloseHandle(hSession); return false; } // A WinHttp session is needed for certain calls if (!hSession) { hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) return false; } BOOL res = false; switch (_CurrentPacm) { case PacmDirectConnection: { const char *curlProxy = getProxy(); if ((curlProxy == NULL) || (*curlProxy != NULL)) // try only if a proxy is set, because we get here only when a connection failed { logStr(__FUNCTION__"-info: trying direct connection..."); setProxy(""); res = true; } } break; case PacmHostname: case PacmWPADURL: // WPAD protocol not configured => try to get settings from IE as a fall-back logStr(__FUNCTION__"-info: reading IE proxy config..."); WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieCfg; res = WinHttpGetIEProxyConfigForCurrentUser(&ieCfg); if (res) { res = false; if ((_CurrentPacm == PacmHostname) && ieCfg.lpszProxy) { logStr(__FUNCTION__"-info: using proxy hostname..."); char proxyAddr [2048]; wcstombs(proxyAddr, ieCfg.lpszProxy, 2047); setProxy(proxyAddr); res = true; // ideally, should take ieCfg.lpszProxyBypass into account } else if ((_CurrentPacm == PacmWPADURL) && ieCfg.lpszAutoConfigUrl) { logStr(__FUNCTION__"-info: trying WPAD proxy auto-config from URL..."); res = setProxyFromWPAD((void*)hSession, url, ieCfg.lpszAutoConfigUrl); } } if (ieCfg.lpszProxy) GlobalFree(ieCfg.lpszProxy); if (ieCfg.lpszAutoConfigUrl) GlobalFree(ieCfg.lpszAutoConfigUrl); if (ieCfg.lpszProxyBypass) GlobalFree(ieCfg.lpszProxyBypass); break; case PacmDNS: // Try DNS-based auto-detection (using 'wpad' host), not DHCP-based (said to have issues) logStr(__FUNCTION__"-info: trying WPAD proxy auto-detection..."); res = setProxyFromWPAD((void*)hSession, url, L"http://wpad/wpad.dat"); break; case PacmFullAuto: // Try full auto-detection (including DHCP) as a last resort logStr(__FUNCTION__"-info: trying full proxy auto-detection..."); res = setProxyFromWPAD((void*)hSession, url, NULL); break; default: _CurrentPacm = PacmBeforeFailed; } ++_CurrentPacm; // set-up for next (potential) call if (res) { // Return success to try connecting logStr(__FUNCTION__"-info: proxy set"); WinHttpCloseHandle(hSession); return true; } else { // Try next proxy auto-config method return nextTryAutoConfigureProxy(url, hSession); } } // *************************************************************************** bool CHttpClient::setProxyFromWPAD(void *hSession, const char *url, const wchar_t *wpadwURL) { // Windows-specific code, adapted from Google Chromium (needs XP SP1 or newer) WINHTTP_AUTOPROXY_OPTIONS options = {0}; options.fAutoLogonIfChallenged = TRUE; if (wpadwURL) { options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; options.lpszAutoConfigUrl = wpadwURL; } else { options.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT, options.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; } WINHTTP_PROXY_INFO info = {0}; wchar_t wurl[2048]; mbstowcs(wurl, url, 2047); BOOL res = WinHttpGetProxyForUrl(hSession, wurl, &options, &info); if (res) { // Set proxy res = true; switch (info.dwAccessType) { case WINHTTP_ACCESS_TYPE_NO_PROXY: setProxy(""); break; case WINHTTP_ACCESS_TYPE_NAMED_PROXY: char proxyAddr [2048]; wcstombs(proxyAddr, info.lpszProxy, 2047); setProxy(proxyAddr); break; default: res = false; } } // Clean-up if (info.lpszProxy) GlobalFree(info.lpszProxy); if (info.lpszProxyBypass) GlobalFree(info.lpszProxyBypass); return (bool)res; } // *************************************************************************** const char *CHttpClient::getProxy() const { return curl_easy_getopt_proxy(_Curl); } #endif // ONLINE_MODE