2018-07-11 18:17:21 +00:00
|
|
|
#include "http.h"
|
|
|
|
|
2022-05-09 14:28:28 +00:00
|
|
|
#include <base/log.h>
|
2022-05-13 11:57:35 +00:00
|
|
|
#include <base/math.h>
|
2018-07-11 18:17:21 +00:00
|
|
|
#include <base/system.h>
|
|
|
|
#include <engine/engine.h>
|
|
|
|
#include <engine/external/json-parser/json.h>
|
|
|
|
#include <engine/shared/config.h>
|
|
|
|
#include <engine/storage.h>
|
|
|
|
#include <game/version.h>
|
|
|
|
|
2021-01-21 10:23:40 +00:00
|
|
|
#if !defined(CONF_FAMILY_WINDOWS)
|
2022-02-14 23:22:52 +00:00
|
|
|
#include <csignal>
|
2021-01-21 10:23:40 +00:00
|
|
|
#endif
|
|
|
|
|
2018-07-11 18:17:21 +00:00
|
|
|
#define WIN32_LEAN_AND_MEAN
|
2022-05-05 12:49:25 +00:00
|
|
|
#include <curl/curl.h>
|
|
|
|
#include <curl/easy.h>
|
2018-07-11 18:17:21 +00:00
|
|
|
|
|
|
|
// TODO: Non-global pls?
|
|
|
|
static CURLSH *gs_Share;
|
2020-09-26 19:41:58 +00:00
|
|
|
static LOCK gs_aLocks[CURL_LOCK_DATA_LAST + 1];
|
2022-05-23 15:18:55 +00:00
|
|
|
static bool gs_Initialized = false;
|
2018-07-11 18:17:21 +00:00
|
|
|
|
|
|
|
static int GetLockIndex(int Data)
|
|
|
|
{
|
|
|
|
if(!(0 <= Data && Data < CURL_LOCK_DATA_LAST))
|
|
|
|
{
|
|
|
|
Data = CURL_LOCK_DATA_LAST;
|
|
|
|
}
|
|
|
|
return Data;
|
|
|
|
}
|
|
|
|
|
2020-12-02 18:11:19 +00:00
|
|
|
static void CurlLock(CURL *pHandle, curl_lock_data Data, curl_lock_access Access, void *pUser) ACQUIRE(gs_aLocks[GetLockIndex(Data)])
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
|
|
|
(void)pHandle;
|
|
|
|
(void)Access;
|
|
|
|
(void)pUser;
|
|
|
|
lock_wait(gs_aLocks[GetLockIndex(Data)]);
|
|
|
|
}
|
|
|
|
|
2020-12-02 18:11:19 +00:00
|
|
|
static void CurlUnlock(CURL *pHandle, curl_lock_data Data, void *pUser) RELEASE(gs_aLocks[GetLockIndex(Data)])
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
|
|
|
(void)pHandle;
|
|
|
|
(void)pUser;
|
|
|
|
lock_unlock(gs_aLocks[GetLockIndex(Data)]);
|
|
|
|
}
|
|
|
|
|
2022-05-09 14:28:28 +00:00
|
|
|
int CurlDebug(CURL *pHandle, curl_infotype Type, char *pData, size_t DataSize, void *pUser)
|
|
|
|
{
|
|
|
|
char TypeChar;
|
|
|
|
switch(Type)
|
|
|
|
{
|
|
|
|
case CURLINFO_TEXT:
|
|
|
|
TypeChar = '*';
|
|
|
|
break;
|
|
|
|
case CURLINFO_HEADER_OUT:
|
|
|
|
TypeChar = '<';
|
|
|
|
break;
|
|
|
|
case CURLINFO_HEADER_IN:
|
|
|
|
TypeChar = '>';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
while(const char *pLineEnd = (const char *)memchr(pData, '\n', DataSize))
|
|
|
|
{
|
|
|
|
int LineLength = pLineEnd - pData;
|
|
|
|
log_debug("curl", "%c %.*s", TypeChar, LineLength, pData);
|
|
|
|
pData += LineLength + 1;
|
|
|
|
DataSize -= LineLength + 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-11 18:17:21 +00:00
|
|
|
bool HttpInit(IStorage *pStorage)
|
|
|
|
{
|
|
|
|
if(curl_global_init(CURL_GLOBAL_DEFAULT))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
gs_Share = curl_share_init();
|
|
|
|
if(!gs_Share)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-19 13:10:32 +00:00
|
|
|
// print curl version
|
|
|
|
{
|
|
|
|
curl_version_info_data *pVersion = curl_version_info(CURLVERSION_NOW);
|
|
|
|
dbg_msg("http", "libcurl version %s (compiled = " LIBCURL_VERSION ")", pVersion->version);
|
|
|
|
}
|
|
|
|
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &Lock : gs_aLocks)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2020-10-26 14:14:07 +00:00
|
|
|
Lock = lock_create();
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
curl_share_setopt(gs_Share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
|
|
|
curl_share_setopt(gs_Share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
|
|
|
curl_share_setopt(gs_Share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
|
|
|
curl_share_setopt(gs_Share, CURLSHOPT_LOCKFUNC, CurlLock);
|
|
|
|
curl_share_setopt(gs_Share, CURLSHOPT_UNLOCKFUNC, CurlUnlock);
|
2021-01-21 10:23:40 +00:00
|
|
|
|
|
|
|
#if !defined(CONF_FAMILY_WINDOWS)
|
|
|
|
// As a multithreaded application we have to tell curl to not install signal
|
|
|
|
// handlers and instead ignore SIGPIPE from OpenSSL ourselves.
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
#endif
|
|
|
|
|
2022-05-23 15:18:55 +00:00
|
|
|
gs_Initialized = true;
|
|
|
|
|
2018-07-11 18:17:21 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EscapeUrl(char *pBuf, int Size, const char *pStr)
|
|
|
|
{
|
|
|
|
char *pEsc = curl_easy_escape(0, pStr, 0);
|
|
|
|
str_copy(pBuf, pEsc, Size);
|
|
|
|
curl_free(pEsc);
|
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
CHttpRequest::CHttpRequest(const char *pUrl)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
|
|
|
str_copy(m_aUrl, pUrl, sizeof(m_aUrl));
|
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
CHttpRequest::~CHttpRequest()
|
|
|
|
{
|
|
|
|
if(!m_WriteToFile)
|
|
|
|
{
|
|
|
|
m_BufferSize = 0;
|
|
|
|
m_BufferLength = 0;
|
|
|
|
free(m_pBuffer);
|
|
|
|
m_pBuffer = nullptr;
|
|
|
|
}
|
|
|
|
curl_slist_free_all((curl_slist *)m_pHeaders);
|
|
|
|
m_pHeaders = nullptr;
|
|
|
|
if(m_pBody)
|
|
|
|
{
|
|
|
|
m_BodyLength = 0;
|
|
|
|
free(m_pBody);
|
|
|
|
m_pBody = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CHttpRequest::Run()
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-23 15:18:55 +00:00
|
|
|
dbg_assert(gs_Initialized, "must initialize HTTP before running HTTP requests");
|
2020-09-24 07:52:11 +00:00
|
|
|
int FinalState;
|
2019-04-04 19:38:52 +00:00
|
|
|
if(!BeforeInit())
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2020-09-24 07:52:11 +00:00
|
|
|
FinalState = HTTP_ERROR;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CURL *pHandle = curl_easy_init();
|
|
|
|
FinalState = RunImpl(pHandle);
|
|
|
|
curl_easy_cleanup(pHandle);
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
|
2020-09-24 07:52:11 +00:00
|
|
|
m_State = OnCompletion(FinalState);
|
2019-04-04 19:38:52 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
bool CHttpRequest::BeforeInit()
|
2019-04-04 19:38:52 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
if(m_WriteToFile)
|
|
|
|
{
|
|
|
|
if(fs_makedir_rec_for(m_aDestAbsolute) < 0)
|
|
|
|
{
|
|
|
|
dbg_msg("http", "i/o error, cannot create folder for: %s", m_aDest);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_File = io_open(m_aDestAbsolute, IOFLAG_WRITE);
|
|
|
|
if(!m_File)
|
|
|
|
{
|
|
|
|
dbg_msg("http", "i/o error, cannot open file: %s", m_aDest);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int CHttpRequest::RunImpl(CURL *pUser)
|
|
|
|
{
|
|
|
|
CURL *pHandle = (CURL *)pUser;
|
2018-07-11 18:17:21 +00:00
|
|
|
if(!pHandle)
|
|
|
|
{
|
2019-04-04 19:38:52 +00:00
|
|
|
return HTTP_ERROR;
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(g_Config.m_DbgCurl)
|
|
|
|
{
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_VERBOSE, 1L);
|
2022-05-09 14:28:28 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_DEBUGFUNCTION, CurlDebug);
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
char aErr[CURL_ERROR_SIZE];
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_ERRORBUFFER, aErr);
|
|
|
|
|
2020-09-04 17:56:30 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_CONNECTTIMEOUT_MS, m_Timeout.ConnectTimeoutMs);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_LIMIT, m_Timeout.LowSpeedLimit);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_TIME, m_Timeout.LowSpeedTime);
|
|
|
|
|
2018-07-11 18:17:21 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_SHARE, gs_Share);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_MAXREDIRS, 4L);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_FAILONERROR, 1L);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_URL, m_aUrl);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_NOSIGNAL, 1L);
|
2020-08-18 13:03:09 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_USERAGENT, GAME_NAME " " GAME_RELEASE_VERSION " (" CONF_PLATFORM_STRING "; " CONF_ARCH_STRING ")");
|
2021-03-07 21:41:34 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_ACCEPT_ENCODING, ""); // Use any compression algorithm supported by libcurl.
|
2018-07-11 18:17:21 +00:00
|
|
|
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_WRITEDATA, this);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_NOPROGRESS, 0L);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_PROGRESSDATA, this);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback);
|
2022-03-07 14:01:37 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_IPRESOLVE, m_IpResolve == IPRESOLVE::V4 ? CURL_IPRESOLVE_V4 : m_IpResolve == IPRESOLVE::V6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_WHATEVER);
|
2018-07-11 18:17:21 +00:00
|
|
|
|
2022-05-05 14:43:35 +00:00
|
|
|
if(curl_version_info(CURLVERSION_NOW)->version_num < 0x074400)
|
2021-11-27 19:39:49 +00:00
|
|
|
{
|
2022-05-05 14:43:35 +00:00
|
|
|
// Causes crashes, see https://github.com/ddnet/ddnet/issues/4342.
|
|
|
|
// No longer a problem in curl 7.68 and above, and 0x44 = 68.
|
2021-11-27 19:39:49 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_FORBID_REUSE, 1L);
|
|
|
|
}
|
|
|
|
|
2021-08-24 10:18:20 +00:00
|
|
|
#ifdef CONF_PLATFORM_ANDROID
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_CAINFO, "data/cacert.pem");
|
|
|
|
#endif
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
switch(m_Type)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
case REQUEST::GET:
|
|
|
|
break;
|
|
|
|
case REQUEST::HEAD:
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_NOBODY, 1L);
|
|
|
|
break;
|
2022-05-05 14:06:49 +00:00
|
|
|
case REQUEST::POST:
|
2022-05-05 12:49:25 +00:00
|
|
|
case REQUEST::POST_JSON:
|
2022-05-05 14:06:49 +00:00
|
|
|
if(m_Type == REQUEST::POST_JSON)
|
|
|
|
{
|
|
|
|
Header("Content-Type: application/json");
|
|
|
|
}
|
2022-05-09 14:28:28 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
Header("Content-Type:");
|
|
|
|
}
|
2022-05-05 12:49:25 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_POSTFIELDS, m_pBody);
|
|
|
|
curl_easy_setopt(pHandle, CURLOPT_POSTFIELDSIZE, m_BodyLength);
|
|
|
|
break;
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
curl_easy_setopt(pHandle, CURLOPT_HTTPHEADER, m_pHeaders);
|
|
|
|
|
2021-06-14 22:12:06 +00:00
|
|
|
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL)
|
2021-05-28 10:11:59 +00:00
|
|
|
dbg_msg("http", "fetching %s", m_aUrl);
|
2018-07-11 18:17:21 +00:00
|
|
|
m_State = HTTP_RUNNING;
|
|
|
|
int Ret = curl_easy_perform(pHandle);
|
|
|
|
if(Ret != CURLE_OK)
|
|
|
|
{
|
2021-06-14 22:12:06 +00:00
|
|
|
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE)
|
|
|
|
dbg_msg("http", "%s failed. libcurl error: %s", m_aUrl, aErr);
|
2019-04-04 19:38:52 +00:00
|
|
|
return (Ret == CURLE_ABORTED_BY_CALLBACK) ? HTTP_ABORTED : HTTP_ERROR;
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-06-14 22:12:06 +00:00
|
|
|
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL)
|
2020-09-22 13:10:35 +00:00
|
|
|
dbg_msg("http", "task done %s", m_aUrl);
|
2019-04-04 19:38:52 +00:00
|
|
|
return HTTP_DONE;
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
size_t CHttpRequest::OnData(char *pData, size_t DataSize)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
if(!m_WriteToFile)
|
|
|
|
{
|
|
|
|
if(DataSize == 0)
|
|
|
|
{
|
|
|
|
return DataSize;
|
|
|
|
}
|
2022-05-13 11:57:35 +00:00
|
|
|
size_t NewBufferSize = maximum((size_t)1024, m_BufferSize);
|
|
|
|
while(m_BufferLength + DataSize > NewBufferSize)
|
2022-05-05 12:49:25 +00:00
|
|
|
{
|
2022-05-13 11:57:35 +00:00
|
|
|
NewBufferSize *= 2;
|
2022-05-05 12:49:25 +00:00
|
|
|
}
|
2022-05-13 11:57:35 +00:00
|
|
|
if(NewBufferSize != m_BufferSize)
|
2022-05-05 12:49:25 +00:00
|
|
|
{
|
2022-05-13 11:57:35 +00:00
|
|
|
m_pBuffer = (unsigned char *)realloc(m_pBuffer, NewBufferSize);
|
|
|
|
m_BufferSize = NewBufferSize;
|
2022-05-05 12:49:25 +00:00
|
|
|
}
|
|
|
|
mem_copy(m_pBuffer + m_BufferLength, pData, DataSize);
|
|
|
|
m_BufferLength += DataSize;
|
|
|
|
return DataSize;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return io_write(m_File, pData, DataSize);
|
|
|
|
}
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
size_t CHttpRequest::WriteCallback(char *pData, size_t Size, size_t Number, void *pUser)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
return ((CHttpRequest *)pUser)->OnData(pData, Size * Number);
|
|
|
|
}
|
|
|
|
|
|
|
|
int CHttpRequest::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr)
|
|
|
|
{
|
|
|
|
CHttpRequest *pTask = (CHttpRequest *)pUser;
|
2021-10-28 11:50:01 +00:00
|
|
|
pTask->m_Current.store(DlCurr, std::memory_order_relaxed);
|
|
|
|
pTask->m_Size.store(DlTotal, std::memory_order_relaxed);
|
|
|
|
pTask->m_Progress.store((100 * DlCurr) / (DlTotal ? DlTotal : 1), std::memory_order_relaxed);
|
2018-07-11 18:17:21 +00:00
|
|
|
pTask->OnProgress();
|
|
|
|
return pTask->m_Abort ? -1 : 0;
|
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
int CHttpRequest::OnCompletion(int State)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
if(m_WriteToFile)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
if(m_File && io_close(m_File) != 0)
|
|
|
|
{
|
|
|
|
dbg_msg("http", "i/o error, cannot close file: %s", m_aDest);
|
|
|
|
State = HTTP_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(State == HTTP_ERROR || State == HTTP_ABORTED)
|
|
|
|
{
|
|
|
|
fs_remove(m_aDestAbsolute);
|
|
|
|
}
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
2022-05-05 12:49:25 +00:00
|
|
|
return State;
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
void CHttpRequest::WriteToFile(IStorage *pStorage, const char *pDest, int StorageType)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
m_WriteToFile = true;
|
|
|
|
str_copy(m_aDest, pDest, sizeof(m_aDest));
|
|
|
|
if(StorageType == -2)
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
pStorage->GetBinaryPath(m_aDest, m_aDestAbsolute, sizeof(m_aDestAbsolute));
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
2022-05-05 12:49:25 +00:00
|
|
|
else
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
pStorage->GetCompletePath(StorageType, m_aDest, m_aDestAbsolute, sizeof(m_aDestAbsolute));
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
2020-09-21 22:51:10 +00:00
|
|
|
}
|
2018-07-11 18:17:21 +00:00
|
|
|
|
2022-05-05 14:06:49 +00:00
|
|
|
void CHttpRequest::Header(const char *pNameColonValue)
|
|
|
|
{
|
|
|
|
m_pHeaders = curl_slist_append((curl_slist *)m_pHeaders, pNameColonValue);
|
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
void CHttpRequest::Result(unsigned char **ppResult, size_t *pResultLength) const
|
2020-09-21 22:51:10 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
if(m_WriteToFile || State() != HTTP_DONE)
|
2019-04-04 19:38:52 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
*ppResult = nullptr;
|
|
|
|
*pResultLength = 0;
|
|
|
|
return;
|
2019-04-04 19:38:52 +00:00
|
|
|
}
|
2022-05-05 12:49:25 +00:00
|
|
|
*ppResult = m_pBuffer;
|
|
|
|
*pResultLength = m_BufferLength;
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
json_value *CHttpRequest::ResultJson() const
|
2018-07-11 18:17:21 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
unsigned char *pResult;
|
|
|
|
size_t ResultLength;
|
|
|
|
Result(&pResult, &ResultLength);
|
|
|
|
if(!pResult)
|
2020-09-21 22:51:10 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
return nullptr;
|
2020-09-21 22:51:10 +00:00
|
|
|
}
|
2022-05-05 12:49:25 +00:00
|
|
|
return json_parse((char *)pResult, ResultLength);
|
2018-07-11 18:17:21 +00:00
|
|
|
}
|