mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Merge #5069
5069: Some HTTP fixes factored out of the HTTP masterserver PR r=def- a=heinrich5991 CC #5064 Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
commit
a041ff8aed
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -74,6 +74,7 @@ versionsrv
|
|||
!src/twping
|
||||
|
||||
generated
|
||||
target
|
||||
|
||||
/build*
|
||||
|
||||
|
|
|
@ -606,7 +606,7 @@ if(CLIENT AND VULKAN)
|
|||
show_dependency_status("Vulkan" VULKAN)
|
||||
endif()
|
||||
|
||||
if(CLIENT AND NOT(CURL_FOUND))
|
||||
if(NOT(CURL_FOUND))
|
||||
message(SEND_ERROR "You must install Curl to compile DDNet")
|
||||
endif()
|
||||
if(NOT(PYTHONINTERP_FOUND))
|
||||
|
@ -1713,6 +1713,8 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
|
|||
filecollection.cpp
|
||||
filecollection.h
|
||||
global_uuid_manager.cpp
|
||||
http.cpp
|
||||
http.h
|
||||
huffman.cpp
|
||||
huffman.h
|
||||
image_manipulation.cpp
|
||||
|
@ -1825,6 +1827,7 @@ set(DEPS ${DEP_JSON} ${DEP_MD5} ${ZLIB_DEP})
|
|||
# Libraries
|
||||
set(LIBS
|
||||
${CRYPTO_LIBRARIES}
|
||||
${CURL_LIBRARIES}
|
||||
${SQLite3_LIBRARIES}
|
||||
${WEBSOCKETS_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
|
@ -1909,8 +1912,6 @@ if(CLIENT)
|
|||
graphics_defines.h
|
||||
graphics_threaded.cpp
|
||||
graphics_threaded.h
|
||||
http.cpp
|
||||
http.h
|
||||
input.cpp
|
||||
input.h
|
||||
keynames.h
|
||||
|
@ -2067,7 +2068,6 @@ if(CLIENT)
|
|||
# Libraries
|
||||
set(LIBS_CLIENT
|
||||
${LIBS}
|
||||
${CURL_LIBRARIES}
|
||||
${FREETYPE_LIBRARIES}
|
||||
${GLEW_LIBRARIES}
|
||||
${PNGLITE_LIBRARIES}
|
||||
|
@ -2143,7 +2143,6 @@ if(CLIENT)
|
|||
endif()
|
||||
|
||||
target_include_directories(${TARGET_CLIENT} SYSTEM PRIVATE
|
||||
${CURL_INCLUDE_DIRS}
|
||||
${FREETYPE_INCLUDE_DIRS}
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
${OGG_INCLUDE_DIRS}
|
||||
|
@ -2490,8 +2489,6 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
|||
set(TESTS_EXTRA
|
||||
src/engine/client/blocklist_driver.cpp
|
||||
src/engine/client/blocklist_driver.h
|
||||
src/engine/client/http.cpp
|
||||
src/engine/client/http.h
|
||||
src/engine/client/serverbrowser.cpp
|
||||
src/engine/client/serverbrowser.h
|
||||
src/engine/client/serverbrowser_http.cpp
|
||||
|
@ -2523,7 +2520,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
|||
${DEPS}
|
||||
)
|
||||
target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${CURL_LIBRARIES} ${MYSQL_LIBRARIES} ${GTEST_LIBRARIES})
|
||||
target_include_directories(${TARGET_TESTRUNNER} SYSTEM PRIVATE ${CURL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS})
|
||||
target_include_directories(${TARGET_TESTRUNNER} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS})
|
||||
|
||||
list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER})
|
||||
list(APPEND TARGETS_LINK ${TARGET_TESTRUNNER})
|
||||
|
@ -3003,7 +3000,7 @@ foreach(target ${TARGETS_OWN})
|
|||
target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR}/src)
|
||||
target_include_directories(${target} PRIVATE src)
|
||||
target_compile_definitions(${target} PRIVATE $<$<CONFIG:Debug>:CONF_DEBUG>)
|
||||
target_include_directories(${target} SYSTEM PRIVATE ${SQLite3_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
|
||||
target_include_directories(${target} SYSTEM PRIVATE ${CURL_INCLUDE_DIRS} ${SQLite3_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
|
||||
target_compile_definitions(${target} PRIVATE GLEW_STATIC)
|
||||
if(CRYPTO_FOUND)
|
||||
target_compile_definitions(${target} PRIVATE CONF_OPENSSL)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 44a5db01b7f3ffa984fd291bb945684c2980207d
|
||||
Subproject commit d0b31d605c5b75e61fd7ec451e339a12945bf1a6
|
|
@ -3012,6 +3012,152 @@ int str_hex_decode(void *dst, int dst_size, const char *src)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void str_base64(char *dst, int dst_size, const void *data_raw, int data_size)
|
||||
{
|
||||
static const char DIGITS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
const unsigned char *data = (const unsigned char *)data_raw;
|
||||
unsigned value = 0;
|
||||
int num_bits = 0;
|
||||
int i = 0;
|
||||
int o = 0;
|
||||
|
||||
dst_size -= 1;
|
||||
dst[dst_size] = 0;
|
||||
while(true)
|
||||
{
|
||||
if(num_bits < 6 && i < data_size)
|
||||
{
|
||||
value = (value << 8) | data[i];
|
||||
num_bits += 8;
|
||||
i += 1;
|
||||
}
|
||||
if(o == dst_size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(num_bits > 0)
|
||||
{
|
||||
unsigned padded;
|
||||
if(num_bits >= 6)
|
||||
{
|
||||
padded = (value >> (num_bits - 6)) & 0x3f;
|
||||
}
|
||||
else
|
||||
{
|
||||
padded = (value << (6 - num_bits)) & 0x3f;
|
||||
}
|
||||
dst[o] = DIGITS[padded];
|
||||
num_bits -= 6;
|
||||
o += 1;
|
||||
}
|
||||
else if(o % 4 != 0)
|
||||
{
|
||||
dst[o] = '=';
|
||||
o += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst[o] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int base64_digit_value(char digit)
|
||||
{
|
||||
if('A' <= digit && digit <= 'Z')
|
||||
{
|
||||
return digit - 'A';
|
||||
}
|
||||
else if('a' <= digit && digit <= 'z')
|
||||
{
|
||||
return digit - 'a' + 26;
|
||||
}
|
||||
else if('0' <= digit && digit <= '9')
|
||||
{
|
||||
return digit - '0' + 52;
|
||||
}
|
||||
else if(digit == '+')
|
||||
{
|
||||
return 62;
|
||||
}
|
||||
else if(digit == '/')
|
||||
{
|
||||
return 63;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int str_base64_decode(void *dst_raw, int dst_size, const char *data)
|
||||
{
|
||||
unsigned char *dst = (unsigned char *)dst_raw;
|
||||
int data_len = str_length(data);
|
||||
|
||||
int i;
|
||||
int o = 0;
|
||||
|
||||
if(data_len % 4 != 0)
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
if(data_len / 4 * 3 > dst_size)
|
||||
{
|
||||
// Output buffer too small.
|
||||
return -2;
|
||||
}
|
||||
for(i = 0; i < data_len; i += 4)
|
||||
{
|
||||
int num_output_bytes = 3;
|
||||
char copy[4];
|
||||
int d[4];
|
||||
int value;
|
||||
int b;
|
||||
mem_copy(copy, data + i, sizeof(copy));
|
||||
if(i == data_len - 4)
|
||||
{
|
||||
if(copy[3] == '=')
|
||||
{
|
||||
copy[3] = 'A';
|
||||
num_output_bytes = 2;
|
||||
if(copy[2] == '=')
|
||||
{
|
||||
copy[2] = 'A';
|
||||
num_output_bytes = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
d[0] = base64_digit_value(copy[0]);
|
||||
d[1] = base64_digit_value(copy[1]);
|
||||
d[2] = base64_digit_value(copy[2]);
|
||||
d[3] = base64_digit_value(copy[3]);
|
||||
if(d[0] == -1 || d[1] == -1 || d[2] == -1 || d[3] == -1)
|
||||
{
|
||||
// Invalid digit.
|
||||
return -1;
|
||||
}
|
||||
value = (d[0] << 18) | (d[1] << 12) | (d[2] << 6) | d[3];
|
||||
for(b = 0; b < 3; b++)
|
||||
{
|
||||
unsigned char byte_value = (value >> (16 - 8 * b)) & 0xff;
|
||||
if(b < num_output_bytes)
|
||||
{
|
||||
dst[o] = byte_value;
|
||||
o += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(byte_value != 0)
|
||||
{
|
||||
// Padding not zeroed.
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
||||
|
|
|
@ -84,7 +84,11 @@ bool dbg_assert_has_failed();
|
|||
*
|
||||
* @see dbg_assert
|
||||
*/
|
||||
void dbg_break();
|
||||
#if defined(__cplusplus)
|
||||
[[noreturn]]
|
||||
#endif
|
||||
void
|
||||
dbg_break();
|
||||
|
||||
/**
|
||||
* Prints a debug message.
|
||||
|
@ -1592,6 +1596,41 @@ void str_hex(char *dst, int dst_size, const void *data, int data_size);
|
|||
- The contents of the buffer is only valid on success
|
||||
*/
|
||||
int str_hex_decode(void *dst, int dst_size, const char *src);
|
||||
|
||||
/*
|
||||
Function: str_base64
|
||||
Takes a datablock and generates the base64 encoding of it.
|
||||
|
||||
Parameters:
|
||||
dst - Buffer to fill with base64 data
|
||||
dst_size - Size of the buffer
|
||||
data - Data to turn into base64
|
||||
data - Size of the data
|
||||
|
||||
Remarks:
|
||||
- The destination buffer will be zero-terminated
|
||||
*/
|
||||
void str_base64(char *dst, int dst_size, const void *data, int data_size);
|
||||
|
||||
/*
|
||||
Function: str_base64_decode
|
||||
Takes a base64 string without any whitespace and correct
|
||||
padding and returns a byte array.
|
||||
|
||||
Parameters:
|
||||
dst - Buffer for the byte array
|
||||
dst_size - Size of the buffer
|
||||
data - String to decode
|
||||
|
||||
Returns:
|
||||
<0 - Error
|
||||
>= 0 - Success, length of the resulting byte buffer
|
||||
|
||||
Remarks:
|
||||
- The contents of the buffer is only valid on success
|
||||
*/
|
||||
int str_base64_decode(void *dst, int dst_size, const char *data);
|
||||
|
||||
/*
|
||||
Function: str_timestamp
|
||||
Copies a time stamp in the format year-month-day_hour-minute-second to the string.
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
#include <engine/storage.h>
|
||||
#include <engine/textrender.h>
|
||||
|
||||
#include <engine/client/http.h>
|
||||
#include <engine/client/notifications.h>
|
||||
#include <engine/shared/assertion_logger.h>
|
||||
#include <engine/shared/compression.h>
|
||||
|
@ -43,6 +42,7 @@
|
|||
#include <engine/shared/demo.h>
|
||||
#include <engine/shared/fifo.h>
|
||||
#include <engine/shared/filecollection.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <engine/shared/json.h>
|
||||
#include <engine/shared/network.h>
|
||||
#include <engine/shared/packer.h>
|
||||
|
@ -1721,7 +1721,8 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
|
|||
bool UseConfigUrl = str_comp(g_Config.m_ClMapDownloadUrl, "https://maps2.ddnet.tw") != 0 || m_aMapDownloadUrl[0] == '\0';
|
||||
str_format(aUrl, sizeof(aUrl), "%s/%s", UseConfigUrl ? g_Config.m_ClMapDownloadUrl : m_aMapDownloadUrl, aEscaped);
|
||||
|
||||
m_pMapdownloadTask = std::make_shared<CGetFile>(Storage(), aUrl, m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE, CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime});
|
||||
m_pMapdownloadTask = HttpGetFile(aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
|
||||
m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime});
|
||||
Engine()->AddJob(m_pMapdownloadTask);
|
||||
}
|
||||
else
|
||||
|
@ -4609,7 +4610,9 @@ void CClient::RequestDDNetInfo()
|
|||
}
|
||||
|
||||
// Use ipv4 so we can know the ingame ip addresses of players before they join game servers
|
||||
m_pDDNetInfoTask = std::make_shared<CGetFile>(Storage(), aUrl, m_aDDNetInfoTmp, IStorage::TYPE_SAVE, CTimeout{10000, 500, 10}, HTTPLOG::ALL, IPRESOLVE::V4);
|
||||
m_pDDNetInfoTask = HttpGetFile(aUrl, Storage(), m_aDDNetInfoTmp, IStorage::TYPE_SAVE);
|
||||
m_pDDNetInfoTask->Timeout(CTimeout{10000, 500, 10});
|
||||
m_pDDNetInfoTask->IpResolve(IPRESOLVE::V4);
|
||||
Engine()->AddJob(m_pDDNetInfoTask);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <engine/client/demoedit.h>
|
||||
#include <engine/client/friends.h>
|
||||
#include <engine/client/ghost.h>
|
||||
#include <engine/client/http.h>
|
||||
#include <engine/client/serverbrowser.h>
|
||||
#include <engine/client/updater.h>
|
||||
#include <engine/discord.h>
|
||||
|
@ -25,6 +24,7 @@
|
|||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/demo.h>
|
||||
#include <engine/shared/fifo.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <engine/shared/network.h>
|
||||
#include <engine/sound.h>
|
||||
#include <engine/steam.h>
|
||||
|
@ -180,7 +180,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
|
|||
char m_aCmdEditMap[IO_MAX_PATH_LENGTH];
|
||||
|
||||
// map download
|
||||
std::shared_ptr<CGetFile> m_pMapdownloadTask;
|
||||
std::shared_ptr<CHttpRequest> m_pMapdownloadTask;
|
||||
char m_aMapdownloadFilename[256];
|
||||
char m_aMapdownloadFilenameTemp[256];
|
||||
char m_aMapdownloadName[256];
|
||||
|
@ -198,7 +198,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
|
|||
SHA256_DIGEST m_MapDetailsSha256;
|
||||
|
||||
char m_aDDNetInfoTmp[64];
|
||||
std::shared_ptr<CGetFile> m_pDDNetInfoTask;
|
||||
std::shared_ptr<CHttpRequest> m_pDDNetInfoTask;
|
||||
|
||||
// time
|
||||
CSmoothTime m_GameTime[NUM_DUMMIES];
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "demoedit.h"
|
||||
|
||||
#include <engine/shared/demo.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
CDemoEdit::CDemoEdit(const char *pNetVersion, class CSnapshotDelta *pSnapshotDelta, IStorage *pStorage, const char *pDemo, const char *pDst, int StartTick, int EndTick) :
|
||||
m_SnapshotDelta(*pSnapshotDelta),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#ifndef ENGINE_CLIENT_DEMOEDIT_H
|
||||
#define ENGINE_CLIENT_DEMOEDIT_H
|
||||
|
||||
#include <engine/client/http.h>
|
||||
#include <engine/shared/demo.h>
|
||||
#include <engine/shared/jobs.h>
|
||||
#include <engine/shared/snapshot.h>
|
||||
|
||||
#define CONNECTLINK "ddnet:"
|
||||
class IStorage;
|
||||
|
||||
class CDemoEdit : public IJob
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ class CDemoEdit : public IJob
|
|||
int m_EndTick;
|
||||
|
||||
public:
|
||||
CDemoEdit(const char *pNetVersion, class CSnapshotDelta *pSnapshotDelta, IStorage *pStorage, const char *pDemo, const char *pDst, int StartTick, int EndTick);
|
||||
CDemoEdit(const char *pNetVersion, CSnapshotDelta *pSnapshotDelta, IStorage *pStorage, const char *pDemo, const char *pDst, int StartTick, int EndTick);
|
||||
void Run();
|
||||
char *Destination() { return m_aDst; }
|
||||
};
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
#ifndef ENGINE_CLIENT_HTTP_H
|
||||
#define ENGINE_CLIENT_HTTP_H
|
||||
|
||||
#include <atomic>
|
||||
#include <engine/kernel.h>
|
||||
#include <engine/shared/jobs.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
typedef struct _json_value json_value;
|
||||
typedef void CURL;
|
||||
|
||||
enum
|
||||
{
|
||||
HTTP_ERROR = -1,
|
||||
HTTP_QUEUED,
|
||||
HTTP_RUNNING,
|
||||
HTTP_DONE,
|
||||
HTTP_ABORTED,
|
||||
};
|
||||
|
||||
enum class HTTPLOG
|
||||
{
|
||||
NONE,
|
||||
FAILURE,
|
||||
ALL,
|
||||
};
|
||||
|
||||
enum class IPRESOLVE
|
||||
{
|
||||
WHATEVER,
|
||||
V4,
|
||||
V6,
|
||||
};
|
||||
|
||||
struct CTimeout
|
||||
{
|
||||
long ConnectTimeoutMs;
|
||||
long LowSpeedLimit;
|
||||
long LowSpeedTime;
|
||||
};
|
||||
|
||||
class CRequest : public IJob
|
||||
{
|
||||
// Abort the request with an error if `BeforeInit()` or `AfterInit()`
|
||||
// returns false. Also abort the request if `OnData()` returns
|
||||
// something other than `DataSize`.
|
||||
virtual bool BeforeInit() { return true; }
|
||||
virtual bool AfterInit(void *pCurl) { return true; }
|
||||
virtual size_t OnData(char *pData, size_t DataSize) = 0;
|
||||
|
||||
virtual void OnProgress() {}
|
||||
|
||||
char m_aUrl[256];
|
||||
|
||||
CTimeout m_Timeout;
|
||||
|
||||
std::atomic<double> m_Size;
|
||||
std::atomic<double> m_Current;
|
||||
std::atomic<int> m_Progress;
|
||||
HTTPLOG m_LogProgress;
|
||||
IPRESOLVE m_IpResolve;
|
||||
|
||||
std::atomic<int> m_State;
|
||||
std::atomic<bool> m_Abort;
|
||||
|
||||
static int ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr);
|
||||
static size_t WriteCallback(char *pData, size_t Size, size_t Number, void *pUser);
|
||||
|
||||
void Run();
|
||||
int RunImpl(CURL *pHandle);
|
||||
|
||||
protected:
|
||||
virtual int OnCompletion(int State) { return State; }
|
||||
|
||||
public:
|
||||
CRequest(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress = HTTPLOG::ALL, IPRESOLVE IpResolve = IPRESOLVE::WHATEVER);
|
||||
|
||||
double Current() const { return m_Current.load(std::memory_order_relaxed); }
|
||||
double Size() const { return m_Size.load(std::memory_order_relaxed); }
|
||||
int Progress() const { return m_Progress.load(std::memory_order_relaxed); }
|
||||
int State() const { return m_State; }
|
||||
void Abort() { m_Abort = true; }
|
||||
};
|
||||
|
||||
class CHead : public CRequest
|
||||
{
|
||||
virtual size_t OnData(char *pData, size_t DataSize) { return DataSize; }
|
||||
virtual bool AfterInit(void *pCurl);
|
||||
|
||||
public:
|
||||
CHead(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress = HTTPLOG::ALL);
|
||||
~CHead();
|
||||
};
|
||||
|
||||
class CGet : public CRequest
|
||||
{
|
||||
virtual size_t OnData(char *pData, size_t DataSize);
|
||||
|
||||
size_t m_BufferSize;
|
||||
size_t m_BufferLength;
|
||||
unsigned char *m_pBuffer;
|
||||
|
||||
public:
|
||||
CGet(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress = HTTPLOG::ALL);
|
||||
~CGet();
|
||||
|
||||
size_t ResultSize() const
|
||||
{
|
||||
if(!Result())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_BufferSize;
|
||||
}
|
||||
}
|
||||
unsigned char *Result() const;
|
||||
unsigned char *TakeResult();
|
||||
json_value *ResultJson() const;
|
||||
};
|
||||
|
||||
class CGetFile : public CRequest
|
||||
{
|
||||
virtual size_t OnData(char *pData, size_t DataSize);
|
||||
virtual bool BeforeInit();
|
||||
|
||||
IStorage *m_pStorage;
|
||||
|
||||
char m_aDestFull[IO_MAX_PATH_LENGTH];
|
||||
IOHANDLE m_File;
|
||||
|
||||
protected:
|
||||
char m_aDest[IO_MAX_PATH_LENGTH];
|
||||
int m_StorageType;
|
||||
|
||||
virtual int OnCompletion(int State);
|
||||
|
||||
public:
|
||||
CGetFile(IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType = -2, CTimeout Timeout = CTimeout{4000, 500, 5}, HTTPLOG LogProgress = HTTPLOG::ALL, IPRESOLVE IpResolve = IPRESOLVE::WHATEVER);
|
||||
|
||||
const char *Dest() const { return m_aDest; }
|
||||
};
|
||||
|
||||
class CPostJson : public CRequest
|
||||
{
|
||||
virtual size_t OnData(char *pData, size_t DataSize) { return DataSize; }
|
||||
virtual bool AfterInit(void *pCurl);
|
||||
|
||||
char m_aJson[1024];
|
||||
|
||||
public:
|
||||
CPostJson(const char *pUrl, CTimeout Timeout, const char *pJson);
|
||||
};
|
||||
|
||||
bool HttpInit(IStorage *pStorage);
|
||||
void EscapeUrl(char *pBuf, int Size, const char *pStr);
|
||||
#endif // ENGINE_CLIENT_HTTP_H
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <vector>
|
||||
|
||||
#include <base/hash_ctxt.h>
|
||||
#include <base/math.h>
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
#ifndef ENGINE_CLIENT_SERVERBROWSER_H
|
||||
#define ENGINE_CLIENT_SERVERBROWSER_H
|
||||
|
||||
#include <engine/client/http.h>
|
||||
#include <engine/config.h>
|
||||
#include <engine/console.h>
|
||||
#include <engine/external/json-parser/json.h>
|
||||
#include <engine/masterserver.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <engine/shared/memheap.h>
|
||||
|
||||
class IServerBrowserHttp;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
#include "serverbrowser_http.h"
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#include <engine/console.h>
|
||||
#include <engine/engine.h>
|
||||
#include <engine/external/json-parser/json.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <engine/shared/jobs.h>
|
||||
#include <engine/shared/linereader.h>
|
||||
#include <engine/shared/serverinfo.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class CChooseMaster
|
||||
{
|
||||
|
@ -46,8 +46,8 @@ private:
|
|||
{
|
||||
LOCK m_Lock;
|
||||
std::shared_ptr<CData> m_pData;
|
||||
std::unique_ptr<CHead> m_pHead PT_GUARDED_BY(m_Lock);
|
||||
std::unique_ptr<CGet> m_pGet PT_GUARDED_BY(m_Lock);
|
||||
std::unique_ptr<CHttpRequest> m_pHead PT_GUARDED_BY(m_Lock);
|
||||
std::unique_ptr<CHttpRequest> m_pGet PT_GUARDED_BY(m_Lock);
|
||||
virtual void Run();
|
||||
|
||||
public:
|
||||
|
@ -166,9 +166,11 @@ void CChooseMaster::CJob::Run()
|
|||
{
|
||||
aTimeMs[i] = -1;
|
||||
const char *pUrl = m_pData->m_aaUrls[aRandomized[i]];
|
||||
CHead *pHead = new CHead(pUrl, Timeout, HTTPLOG::FAILURE);
|
||||
CHttpRequest *pHead = HttpHead(pUrl).release();
|
||||
pHead->Timeout(Timeout);
|
||||
pHead->LogProgress(HTTPLOG::FAILURE);
|
||||
lock_wait(m_Lock);
|
||||
m_pHead = std::unique_ptr<CHead>(pHead);
|
||||
m_pHead = std::unique_ptr<CHttpRequest>(pHead);
|
||||
lock_unlock(m_Lock);
|
||||
IEngine::RunJobBlocking(pHead);
|
||||
if(pHead->State() == HTTP_ABORTED)
|
||||
|
@ -181,9 +183,11 @@ void CChooseMaster::CJob::Run()
|
|||
continue;
|
||||
}
|
||||
int64_t StartTime = time_get_microseconds();
|
||||
CGet *pGet = new CGet(pUrl, Timeout, HTTPLOG::FAILURE);
|
||||
CHttpRequest *pGet = HttpGet(pUrl).release();
|
||||
pGet->Timeout(Timeout);
|
||||
pGet->LogProgress(HTTPLOG::FAILURE);
|
||||
lock_wait(m_Lock);
|
||||
m_pGet = std::unique_ptr<CGet>(pGet);
|
||||
m_pGet = std::unique_ptr<CHttpRequest>(pGet);
|
||||
lock_unlock(m_Lock);
|
||||
IEngine::RunJobBlocking(pGet);
|
||||
int Time = (time_get_microseconds() - StartTime) / 1000;
|
||||
|
@ -290,7 +294,7 @@ private:
|
|||
IConsole *m_pConsole;
|
||||
|
||||
int m_State = STATE_DONE;
|
||||
std::shared_ptr<CGet> m_pGetServers;
|
||||
std::shared_ptr<CHttpRequest> m_pGetServers;
|
||||
std::unique_ptr<CChooseMaster> m_pChooseMaster;
|
||||
|
||||
std::vector<CEntry> m_aServers;
|
||||
|
@ -327,9 +331,10 @@ void CServerBrowserHttp::Update()
|
|||
}
|
||||
return;
|
||||
}
|
||||
m_pGetServers = HttpGet(pBestUrl);
|
||||
// 10 seconds connection timeout, lower than 8KB/s for 10 seconds to fail.
|
||||
CTimeout Timeout{10000, 8000, 10};
|
||||
m_pEngine->AddJob(m_pGetServers = std::make_shared<CGet>(pBestUrl, Timeout));
|
||||
m_pGetServers->Timeout(CTimeout{10000, 8000, 10});
|
||||
m_pEngine->AddJob(m_pGetServers);
|
||||
m_State = STATE_REFRESHING;
|
||||
}
|
||||
else if(m_State == STATE_REFRESHING)
|
||||
|
@ -339,7 +344,7 @@ void CServerBrowserHttp::Update()
|
|||
return;
|
||||
}
|
||||
m_State = STATE_DONE;
|
||||
std::shared_ptr<CGet> pGetServers = nullptr;
|
||||
std::shared_ptr<CHttpRequest> pGetServers = nullptr;
|
||||
std::swap(m_pGetServers, pGetServers);
|
||||
|
||||
bool Success = true;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
using std::map;
|
||||
using std::string;
|
||||
|
||||
class CUpdaterFetchTask : public CGetFile
|
||||
class CUpdaterFetchTask : public CHttpRequest
|
||||
{
|
||||
char m_aBuf[256];
|
||||
char m_aBuf2[256];
|
||||
|
@ -44,9 +44,10 @@ static const char *GetUpdaterDestPath(char *pBuf, int BufSize, const char *pFile
|
|||
}
|
||||
|
||||
CUpdaterFetchTask::CUpdaterFetchTask(CUpdater *pUpdater, const char *pFile, const char *pDestPath) :
|
||||
CGetFile(pUpdater->m_pStorage, GetUpdaterUrl(m_aBuf, sizeof(m_aBuf), pFile), GetUpdaterDestPath(m_aBuf2, sizeof(m_aBuf), pFile, pDestPath), -2, CTimeout{0, 0, 0}),
|
||||
CHttpRequest(GetUpdaterUrl(m_aBuf, sizeof(m_aBuf), pFile)),
|
||||
m_pUpdater(pUpdater)
|
||||
{
|
||||
WriteToFile(pUpdater->m_pStorage, GetUpdaterDestPath(m_aBuf2, sizeof(m_aBuf2), pFile, pDestPath), -2);
|
||||
}
|
||||
|
||||
void CUpdaterFetchTask::OnProgress()
|
||||
|
@ -59,7 +60,7 @@ void CUpdaterFetchTask::OnProgress()
|
|||
|
||||
int CUpdaterFetchTask::OnCompletion(int State)
|
||||
{
|
||||
State = CGetFile::OnCompletion(State);
|
||||
State = CHttpRequest::OnCompletion(State);
|
||||
|
||||
const char *b = 0;
|
||||
for(const char *a = Dest(); *a; a++)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef ENGINE_CLIENT_UPDATER_H
|
||||
#define ENGINE_CLIENT_UPDATER_H
|
||||
|
||||
#include <engine/client/http.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <engine/updater.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
|
||||
#include <engine/map.h>
|
||||
#include <engine/shared/protocol.h>
|
||||
#include <game/client/ui.h>
|
||||
|
||||
#include "kernel.h"
|
||||
|
||||
#define DDNET_INFO "ddnet-info.json"
|
||||
|
||||
class CUIElement;
|
||||
|
||||
class CServerInfo
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
#include <engine/shared/video.h>
|
||||
#endif
|
||||
|
||||
#include <game/generated/protocol.h>
|
||||
|
||||
#include "compression.h"
|
||||
#include "demo.h"
|
||||
#include "memheap.h"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "http.h"
|
||||
|
||||
#include <base/math.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/engine.h>
|
||||
#include <engine/external/json-parser/json.h>
|
||||
|
@ -12,8 +13,8 @@
|
|||
#endif
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
#include <curl/curl.h>
|
||||
#include <curl/easy.h>
|
||||
|
||||
// TODO: Non-global pls?
|
||||
static CURLSH *gs_Share;
|
||||
|
@ -86,19 +87,31 @@ void EscapeUrl(char *pBuf, int Size, const char *pStr)
|
|||
curl_free(pEsc);
|
||||
}
|
||||
|
||||
CRequest::CRequest(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress, IPRESOLVE IpResolve) :
|
||||
m_Timeout(Timeout),
|
||||
m_Size(0),
|
||||
m_Progress(0),
|
||||
m_LogProgress(LogProgress),
|
||||
m_IpResolve(IpResolve),
|
||||
m_State(HTTP_QUEUED),
|
||||
m_Abort(false)
|
||||
CHttpRequest::CHttpRequest(const char *pUrl)
|
||||
{
|
||||
str_copy(m_aUrl, pUrl, sizeof(m_aUrl));
|
||||
}
|
||||
|
||||
void CRequest::Run()
|
||||
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()
|
||||
{
|
||||
int FinalState;
|
||||
if(!BeforeInit())
|
||||
|
@ -115,8 +128,29 @@ void CRequest::Run()
|
|||
m_State = OnCompletion(FinalState);
|
||||
}
|
||||
|
||||
int CRequest::RunImpl(CURL *pHandle)
|
||||
bool CHttpRequest::BeforeInit()
|
||||
{
|
||||
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;
|
||||
if(!pHandle)
|
||||
{
|
||||
return HTTP_ERROR;
|
||||
|
@ -150,9 +184,10 @@ int CRequest::RunImpl(CURL *pHandle)
|
|||
curl_easy_setopt(pHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback);
|
||||
curl_easy_setopt(pHandle, CURLOPT_IPRESOLVE, m_IpResolve == IPRESOLVE::V4 ? CURL_IPRESOLVE_V4 : m_IpResolve == IPRESOLVE::V6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_WHATEVER);
|
||||
|
||||
if(curl_version_info(CURLVERSION_NOW)->version_num < 0x076800)
|
||||
if(curl_version_info(CURLVERSION_NOW)->version_num < 0x074400)
|
||||
{
|
||||
// Causes crashes, see https://github.com/ddnet/ddnet/issues/4342
|
||||
// Causes crashes, see https://github.com/ddnet/ddnet/issues/4342.
|
||||
// No longer a problem in curl 7.68 and above, and 0x44 = 68.
|
||||
curl_easy_setopt(pHandle, CURLOPT_FORBID_REUSE, 1L);
|
||||
}
|
||||
|
||||
|
@ -160,11 +195,26 @@ int CRequest::RunImpl(CURL *pHandle)
|
|||
curl_easy_setopt(pHandle, CURLOPT_CAINFO, "data/cacert.pem");
|
||||
#endif
|
||||
|
||||
if(!AfterInit(pHandle))
|
||||
switch(m_Type)
|
||||
{
|
||||
return HTTP_ERROR;
|
||||
case REQUEST::GET:
|
||||
break;
|
||||
case REQUEST::HEAD:
|
||||
curl_easy_setopt(pHandle, CURLOPT_NOBODY, 1L);
|
||||
break;
|
||||
case REQUEST::POST:
|
||||
case REQUEST::POST_JSON:
|
||||
if(m_Type == REQUEST::POST_JSON)
|
||||
{
|
||||
Header("Content-Type: application/json");
|
||||
}
|
||||
curl_easy_setopt(pHandle, CURLOPT_POSTFIELDS, m_pBody);
|
||||
curl_easy_setopt(pHandle, CURLOPT_POSTFIELDSIZE, m_BodyLength);
|
||||
break;
|
||||
}
|
||||
|
||||
curl_easy_setopt(pHandle, CURLOPT_HTTPHEADER, m_pHeaders);
|
||||
|
||||
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL)
|
||||
dbg_msg("http", "fetching %s", m_aUrl);
|
||||
m_State = HTTP_RUNNING;
|
||||
|
@ -183,14 +233,42 @@ int CRequest::RunImpl(CURL *pHandle)
|
|||
}
|
||||
}
|
||||
|
||||
size_t CRequest::WriteCallback(char *pData, size_t Size, size_t Number, void *pUser)
|
||||
size_t CHttpRequest::OnData(char *pData, size_t DataSize)
|
||||
{
|
||||
return ((CRequest *)pUser)->OnData(pData, Size * Number);
|
||||
if(!m_WriteToFile)
|
||||
{
|
||||
if(DataSize == 0)
|
||||
{
|
||||
return DataSize;
|
||||
}
|
||||
size_t NewBufferSize = maximum((size_t)1024, m_BufferSize);
|
||||
while(m_BufferLength + DataSize > NewBufferSize)
|
||||
{
|
||||
NewBufferSize *= 2;
|
||||
}
|
||||
if(NewBufferSize != m_BufferSize)
|
||||
{
|
||||
m_pBuffer = (unsigned char *)realloc(m_pBuffer, NewBufferSize);
|
||||
m_BufferSize = NewBufferSize;
|
||||
}
|
||||
mem_copy(m_pBuffer + m_BufferLength, pData, DataSize);
|
||||
m_BufferLength += DataSize;
|
||||
return DataSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
return io_write(m_File, pData, DataSize);
|
||||
}
|
||||
}
|
||||
|
||||
int CRequest::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr)
|
||||
size_t CHttpRequest::WriteCallback(char *pData, size_t Size, size_t Number, void *pUser)
|
||||
{
|
||||
CGetFile *pTask = (CGetFile *)pUser;
|
||||
return ((CHttpRequest *)pUser)->OnData(pData, Size * Number);
|
||||
}
|
||||
|
||||
int CHttpRequest::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr)
|
||||
{
|
||||
CHttpRequest *pTask = (CHttpRequest *)pUser;
|
||||
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);
|
||||
|
@ -198,159 +276,63 @@ int CRequest::ProgressCallback(void *pUser, double DlTotal, double DlCurr, doubl
|
|||
return pTask->m_Abort ? -1 : 0;
|
||||
}
|
||||
|
||||
CHead::CHead(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress) :
|
||||
CRequest(pUrl, Timeout, LogProgress)
|
||||
int CHttpRequest::OnCompletion(int State)
|
||||
{
|
||||
}
|
||||
|
||||
CHead::~CHead() = default;
|
||||
|
||||
bool CHead::AfterInit(void *pCurl)
|
||||
{
|
||||
CURL *pHandle = pCurl;
|
||||
curl_easy_setopt(pHandle, CURLOPT_NOBODY, 1L);
|
||||
return true;
|
||||
}
|
||||
|
||||
CGet::CGet(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress) :
|
||||
CRequest(pUrl, Timeout, LogProgress),
|
||||
m_BufferSize(0),
|
||||
m_BufferLength(0),
|
||||
m_pBuffer(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
CGet::~CGet()
|
||||
{
|
||||
m_BufferSize = 0;
|
||||
m_BufferLength = 0;
|
||||
free(m_pBuffer);
|
||||
m_pBuffer = NULL;
|
||||
}
|
||||
|
||||
unsigned char *CGet::Result() const
|
||||
{
|
||||
if(State() != HTTP_DONE)
|
||||
if(m_WriteToFile)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return m_pBuffer;
|
||||
}
|
||||
if(m_File && io_close(m_File) != 0)
|
||||
{
|
||||
dbg_msg("http", "i/o error, cannot close file: %s", m_aDest);
|
||||
State = HTTP_ERROR;
|
||||
}
|
||||
|
||||
unsigned char *CGet::TakeResult()
|
||||
{
|
||||
unsigned char *pResult = Result();
|
||||
if(pResult)
|
||||
{
|
||||
m_BufferSize = 0;
|
||||
m_BufferLength = 0;
|
||||
m_pBuffer = NULL;
|
||||
if(State == HTTP_ERROR || State == HTTP_ABORTED)
|
||||
{
|
||||
fs_remove(m_aDestAbsolute);
|
||||
}
|
||||
}
|
||||
return pResult;
|
||||
}
|
||||
|
||||
json_value *CGet::ResultJson() const
|
||||
{
|
||||
unsigned char *pResult = Result();
|
||||
if(!pResult)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return json_parse((char *)pResult, m_BufferLength);
|
||||
}
|
||||
|
||||
size_t CGet::OnData(char *pData, size_t DataSize)
|
||||
{
|
||||
if(DataSize == 0)
|
||||
{
|
||||
return DataSize;
|
||||
}
|
||||
bool Reallocate = false;
|
||||
if(m_BufferSize == 0)
|
||||
{
|
||||
m_BufferSize = 1024;
|
||||
Reallocate = true;
|
||||
}
|
||||
while(m_BufferLength + DataSize > m_BufferSize)
|
||||
{
|
||||
m_BufferSize *= 2;
|
||||
Reallocate = true;
|
||||
}
|
||||
if(Reallocate)
|
||||
{
|
||||
m_pBuffer = (unsigned char *)realloc(m_pBuffer, m_BufferSize);
|
||||
}
|
||||
mem_copy(m_pBuffer + m_BufferLength, pData, DataSize);
|
||||
m_BufferLength += DataSize;
|
||||
return DataSize;
|
||||
}
|
||||
|
||||
CGetFile::CGetFile(IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType, CTimeout Timeout, HTTPLOG LogProgress, IPRESOLVE IpResolve) :
|
||||
CRequest(pUrl, Timeout, LogProgress, IpResolve),
|
||||
m_pStorage(pStorage),
|
||||
m_File(0),
|
||||
m_StorageType(StorageType)
|
||||
{
|
||||
str_copy(m_aDest, pDest, sizeof(m_aDest));
|
||||
|
||||
if(m_StorageType == -2)
|
||||
m_pStorage->GetBinaryPath(m_aDest, m_aDestFull, sizeof(m_aDestFull));
|
||||
else
|
||||
m_pStorage->GetCompletePath(m_StorageType, m_aDest, m_aDestFull, sizeof(m_aDestFull));
|
||||
}
|
||||
|
||||
bool CGetFile::BeforeInit()
|
||||
{
|
||||
if(fs_makedir_rec_for(m_aDestFull) < 0)
|
||||
{
|
||||
dbg_msg("http", "i/o error, cannot create folder for: %s", m_aDestFull);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_File = io_open(m_aDestFull, IOFLAG_WRITE);
|
||||
if(!m_File)
|
||||
{
|
||||
dbg_msg("http", "i/o error, cannot open file: %s", m_aDest);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t CGetFile::OnData(char *pData, size_t DataSize)
|
||||
{
|
||||
return io_write(m_File, pData, DataSize);
|
||||
}
|
||||
|
||||
int CGetFile::OnCompletion(int State)
|
||||
{
|
||||
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)
|
||||
{
|
||||
m_pStorage->RemoveFile(m_aDestFull, IStorage::TYPE_ABSOLUTE);
|
||||
}
|
||||
|
||||
return State;
|
||||
}
|
||||
|
||||
CPostJson::CPostJson(const char *pUrl, CTimeout Timeout, const char *pJson) :
|
||||
CRequest(pUrl, Timeout)
|
||||
void CHttpRequest::WriteToFile(IStorage *pStorage, const char *pDest, int StorageType)
|
||||
{
|
||||
str_copy(m_aJson, pJson, sizeof(m_aJson));
|
||||
m_WriteToFile = true;
|
||||
str_copy(m_aDest, pDest, sizeof(m_aDest));
|
||||
if(StorageType == -2)
|
||||
{
|
||||
pStorage->GetBinaryPath(m_aDest, m_aDestAbsolute, sizeof(m_aDestAbsolute));
|
||||
}
|
||||
else
|
||||
{
|
||||
pStorage->GetCompletePath(StorageType, m_aDest, m_aDestAbsolute, sizeof(m_aDestAbsolute));
|
||||
}
|
||||
}
|
||||
|
||||
bool CPostJson::AfterInit(void *pCurl)
|
||||
void CHttpRequest::Header(const char *pNameColonValue)
|
||||
{
|
||||
CURL *pHandle = pCurl;
|
||||
|
||||
curl_slist *pHeaders = NULL;
|
||||
pHeaders = curl_slist_append(pHeaders, "Content-Type: application/json");
|
||||
curl_easy_setopt(pHandle, CURLOPT_HTTPHEADER, pHeaders);
|
||||
curl_easy_setopt(pHandle, CURLOPT_POSTFIELDS, m_aJson);
|
||||
|
||||
return true;
|
||||
m_pHeaders = curl_slist_append((curl_slist *)m_pHeaders, pNameColonValue);
|
||||
}
|
||||
|
||||
void CHttpRequest::Result(unsigned char **ppResult, size_t *pResultLength) const
|
||||
{
|
||||
if(m_WriteToFile || State() != HTTP_DONE)
|
||||
{
|
||||
*ppResult = nullptr;
|
||||
*pResultLength = 0;
|
||||
return;
|
||||
}
|
||||
*ppResult = m_pBuffer;
|
||||
*pResultLength = m_BufferLength;
|
||||
}
|
||||
|
||||
json_value *CHttpRequest::ResultJson() const
|
||||
{
|
||||
unsigned char *pResult;
|
||||
size_t ResultLength;
|
||||
Result(&pResult, &ResultLength);
|
||||
if(!pResult)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return json_parse((char *)pResult, ResultLength);
|
||||
}
|
190
src/engine/shared/http.h
Normal file
190
src/engine/shared/http.h
Normal file
|
@ -0,0 +1,190 @@
|
|||
#ifndef ENGINE_SHARED_HTTP_H
|
||||
#define ENGINE_SHARED_HTTP_H
|
||||
|
||||
#include <atomic>
|
||||
#include <engine/shared/jobs.h>
|
||||
|
||||
typedef struct _json_value json_value;
|
||||
class IStorage;
|
||||
|
||||
enum
|
||||
{
|
||||
HTTP_ERROR = -1,
|
||||
HTTP_QUEUED,
|
||||
HTTP_RUNNING,
|
||||
HTTP_DONE,
|
||||
HTTP_ABORTED,
|
||||
};
|
||||
|
||||
enum class HTTPLOG
|
||||
{
|
||||
NONE,
|
||||
FAILURE,
|
||||
ALL,
|
||||
};
|
||||
|
||||
enum class IPRESOLVE
|
||||
{
|
||||
WHATEVER,
|
||||
V4,
|
||||
V6,
|
||||
};
|
||||
|
||||
struct CTimeout
|
||||
{
|
||||
long ConnectTimeoutMs;
|
||||
long LowSpeedLimit;
|
||||
long LowSpeedTime;
|
||||
};
|
||||
|
||||
class CHttpRequest : public IJob
|
||||
{
|
||||
enum class REQUEST
|
||||
{
|
||||
GET = 0,
|
||||
HEAD,
|
||||
POST,
|
||||
POST_JSON,
|
||||
};
|
||||
char m_aUrl[256] = {0};
|
||||
|
||||
void *m_pHeaders = nullptr;
|
||||
unsigned char *m_pBody = nullptr;
|
||||
size_t m_BodyLength = 0;
|
||||
|
||||
CTimeout m_Timeout = CTimeout{0, 0, 0};
|
||||
REQUEST m_Type = REQUEST::GET;
|
||||
|
||||
bool m_WriteToFile = false;
|
||||
|
||||
// If `m_WriteToFile` is false.
|
||||
size_t m_BufferSize = 0;
|
||||
size_t m_BufferLength = 0;
|
||||
unsigned char *m_pBuffer = nullptr;
|
||||
|
||||
// If `m_WriteToFile` is true.
|
||||
IOHANDLE m_File = nullptr;
|
||||
char m_aDestAbsolute[IO_MAX_PATH_LENGTH] = {0};
|
||||
char m_aDest[IO_MAX_PATH_LENGTH] = {0};
|
||||
|
||||
std::atomic<double> m_Size{0.0};
|
||||
std::atomic<double> m_Current{0.0};
|
||||
std::atomic<int> m_Progress{0};
|
||||
HTTPLOG m_LogProgress = HTTPLOG::ALL;
|
||||
IPRESOLVE m_IpResolve = IPRESOLVE::WHATEVER;
|
||||
|
||||
std::atomic<int> m_State{HTTP_QUEUED};
|
||||
std::atomic<bool> m_Abort{false};
|
||||
|
||||
void Run();
|
||||
// Abort the request with an error if `BeforeInit()` returns false.
|
||||
bool BeforeInit();
|
||||
int RunImpl(void *pUser);
|
||||
|
||||
// Abort the request if `OnData()` returns something other than
|
||||
// `DataSize`.
|
||||
size_t OnData(char *pData, size_t DataSize);
|
||||
|
||||
static int ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr);
|
||||
static size_t WriteCallback(char *pData, size_t Size, size_t Number, void *pUser);
|
||||
|
||||
protected:
|
||||
virtual void OnProgress() {}
|
||||
virtual int OnCompletion(int State);
|
||||
|
||||
public:
|
||||
CHttpRequest(const char *pUrl);
|
||||
~CHttpRequest();
|
||||
|
||||
void Timeout(CTimeout Timeout) { m_Timeout = Timeout; }
|
||||
void LogProgress(HTTPLOG LogProgress) { m_LogProgress = LogProgress; }
|
||||
void IpResolve(IPRESOLVE IpResolve) { m_IpResolve = IpResolve; }
|
||||
void WriteToFile(IStorage *pStorage, const char *pDest, int StorageType);
|
||||
void Head() { m_Type = REQUEST::HEAD; }
|
||||
void Post(const unsigned char *pData, size_t DataLength)
|
||||
{
|
||||
m_Type = REQUEST::POST;
|
||||
m_BodyLength = DataLength;
|
||||
m_pBody = (unsigned char *)malloc(DataLength);
|
||||
mem_copy(m_pBody, pData, DataLength);
|
||||
}
|
||||
void PostJson(const char *pJson)
|
||||
{
|
||||
m_Type = REQUEST::POST_JSON;
|
||||
m_BodyLength = str_length(pJson);
|
||||
m_pBody = (unsigned char *)malloc(m_BodyLength);
|
||||
mem_copy(m_pBody, pJson, m_BodyLength);
|
||||
}
|
||||
void Header(const char *pNameColonValue);
|
||||
void HeaderString(const char *pName, const char *pValue)
|
||||
{
|
||||
char aHeader[256];
|
||||
str_format(aHeader, sizeof(aHeader), "%s: %s", pName, pValue);
|
||||
Header(aHeader);
|
||||
}
|
||||
void HeaderInt(const char *pName, int Value)
|
||||
{
|
||||
char aHeader[256];
|
||||
str_format(aHeader, sizeof(aHeader), "%s: %d", pName, Value);
|
||||
Header(aHeader);
|
||||
}
|
||||
|
||||
const char *Dest()
|
||||
{
|
||||
if(m_WriteToFile)
|
||||
{
|
||||
return m_aDest;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
double Current() const { return m_Current.load(std::memory_order_relaxed); }
|
||||
double Size() const { return m_Size.load(std::memory_order_relaxed); }
|
||||
int Progress() const { return m_Progress.load(std::memory_order_relaxed); }
|
||||
int State() const { return m_State; }
|
||||
void Abort() { m_Abort = true; }
|
||||
|
||||
void Result(unsigned char **ppResult, size_t *pResultLength) const;
|
||||
json_value *ResultJson() const;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CHttpRequest> HttpHead(const char *pUrl)
|
||||
{
|
||||
std::unique_ptr<CHttpRequest> pResult = std::unique_ptr<CHttpRequest>(new CHttpRequest(pUrl));
|
||||
pResult->Head();
|
||||
return pResult;
|
||||
}
|
||||
|
||||
inline std::unique_ptr<CHttpRequest> HttpGet(const char *pUrl)
|
||||
{
|
||||
return std::unique_ptr<CHttpRequest>(new CHttpRequest(pUrl));
|
||||
}
|
||||
|
||||
inline std::unique_ptr<CHttpRequest> HttpGetFile(const char *pUrl, IStorage *pStorage, const char *pOutputFile, int StorageType)
|
||||
{
|
||||
std::unique_ptr<CHttpRequest> pResult = HttpGet(pUrl);
|
||||
pResult->WriteToFile(pStorage, pOutputFile, StorageType);
|
||||
pResult->Timeout(CTimeout{4000, 500, 5});
|
||||
return pResult;
|
||||
}
|
||||
|
||||
inline std::unique_ptr<CHttpRequest> HttpPost(const char *pUrl, const unsigned char *pData, size_t DataLength)
|
||||
{
|
||||
std::unique_ptr<CHttpRequest> pResult = std::unique_ptr<CHttpRequest>(new CHttpRequest(pUrl));
|
||||
pResult->Post(pData, DataLength);
|
||||
return pResult;
|
||||
}
|
||||
|
||||
inline std::unique_ptr<CHttpRequest> HttpPostJson(const char *pUrl, const char *pJson)
|
||||
{
|
||||
std::unique_ptr<CHttpRequest> pResult = std::unique_ptr<CHttpRequest>(new CHttpRequest(pUrl));
|
||||
pResult->PostJson(pJson);
|
||||
return pResult;
|
||||
}
|
||||
|
||||
bool HttpInit(IStorage *pStorage);
|
||||
void EscapeUrl(char *pBuf, int Size, const char *pStr);
|
||||
#endif // ENGINE_SHARED_HTTP_H
|
|
@ -1,9 +1,12 @@
|
|||
#include "serverinfo.h"
|
||||
|
||||
#include "json.h"
|
||||
#include <base/math.h>
|
||||
#include <engine/external/json-parser/json.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
static bool IsAllowedHex(char c)
|
||||
{
|
||||
static const char ALLOWED[] = "0123456789abcdefABCDEF";
|
||||
|
|
|
@ -29,18 +29,22 @@ static bool IsVanillaSkin(const char *pName)
|
|||
|
||||
int CSkins::CGetPngFile::OnCompletion(int State)
|
||||
{
|
||||
State = CGetFile::OnCompletion(State);
|
||||
State = CHttpRequest::OnCompletion(State);
|
||||
|
||||
if(State != HTTP_ERROR && State != HTTP_ABORTED && !m_pSkins->LoadSkinPNG(m_Info, m_aDest, m_aDest, m_StorageType))
|
||||
if(State != HTTP_ERROR && State != HTTP_ABORTED && !m_pSkins->LoadSkinPNG(m_Info, Dest(), Dest(), IStorage::TYPE_SAVE))
|
||||
{
|
||||
State = HTTP_ERROR;
|
||||
}
|
||||
return State;
|
||||
}
|
||||
|
||||
CSkins::CGetPngFile::CGetPngFile(CSkins *pSkins, IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType, CTimeout Timeout, HTTPLOG LogProgress) :
|
||||
CGetFile(pStorage, pUrl, pDest, StorageType, Timeout, LogProgress), m_pSkins(pSkins)
|
||||
CSkins::CGetPngFile::CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest) :
|
||||
CHttpRequest(pUrl),
|
||||
m_pSkins(pSkins)
|
||||
{
|
||||
WriteToFile(pStorage, pDest, IStorage::TYPE_SAVE);
|
||||
Timeout(CTimeout{0, 0, 0});
|
||||
LogProgress(HTTPLOG::NONE);
|
||||
}
|
||||
|
||||
struct SSkinScanUser
|
||||
|
@ -438,7 +442,7 @@ int CSkins::FindImpl(const char *pName)
|
|||
str_format(aUrl, sizeof(aUrl), "%s%s.png", g_Config.m_ClSkinDownloadUrl, aEscapedName);
|
||||
char aBuf[IO_MAX_PATH_LENGTH];
|
||||
str_format(Skin.m_aPath, sizeof(Skin.m_aPath), "downloadedskins/%s", IStorage::FormatTmpPath(aBuf, sizeof(aBuf), pName));
|
||||
Skin.m_pTask = std::make_shared<CGetPngFile>(this, Storage(), aUrl, Skin.m_aPath, IStorage::TYPE_SAVE, CTimeout{0, 0, 0}, HTTPLOG::NONE);
|
||||
Skin.m_pTask = std::make_shared<CGetPngFile>(this, aUrl, Storage(), Skin.m_aPath);
|
||||
m_pClient->Engine()->AddJob(Skin.m_pTask);
|
||||
m_aDownloadSkins.add(Skin);
|
||||
return -1;
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
#include <base/color.h>
|
||||
#include <base/tl/sorted_array.h>
|
||||
#include <base/vmath.h>
|
||||
#include <engine/client/http.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <game/client/component.h>
|
||||
#include <game/client/skin.h>
|
||||
|
||||
class CSkins : public CComponent
|
||||
{
|
||||
public:
|
||||
class CGetPngFile : public CGetFile
|
||||
class CGetPngFile : public CHttpRequest
|
||||
{
|
||||
CSkins *m_pSkins;
|
||||
|
||||
|
@ -20,7 +20,7 @@ public:
|
|||
virtual int OnCompletion(int State) override;
|
||||
|
||||
public:
|
||||
CGetPngFile(CSkins *pSkins, IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType = -2, CTimeout Timeout = CTimeout{4000, 500, 5}, HTTPLOG LogProgress = HTTPLOG::ALL);
|
||||
CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest);
|
||||
CImageInfo m_Info;
|
||||
};
|
||||
|
||||
|
|
|
@ -221,6 +221,84 @@ TEST(Str, HexDecode)
|
|||
EXPECT_STREQ(aOut, "ABCD");
|
||||
}
|
||||
|
||||
void StrBase64Str(char *pBuffer, int BufferSize, const char *pString)
|
||||
{
|
||||
str_base64(pBuffer, BufferSize, pString, str_length(pString));
|
||||
}
|
||||
|
||||
TEST(Str, Base64)
|
||||
{
|
||||
char aBuf[128];
|
||||
str_base64(aBuf, sizeof(aBuf), "\0", 1);
|
||||
EXPECT_STREQ(aBuf, "AA==");
|
||||
str_base64(aBuf, sizeof(aBuf), "\0\0", 2);
|
||||
EXPECT_STREQ(aBuf, "AAA=");
|
||||
str_base64(aBuf, sizeof(aBuf), "\0\0\0", 3);
|
||||
EXPECT_STREQ(aBuf, "AAAA");
|
||||
|
||||
StrBase64Str(aBuf, sizeof(aBuf), "");
|
||||
EXPECT_STREQ(aBuf, "");
|
||||
|
||||
// https://en.wikipedia.org/w/index.php?title=Base64&oldid=1033503483#Output_padding
|
||||
StrBase64Str(aBuf, sizeof(aBuf), "pleasure.");
|
||||
EXPECT_STREQ(aBuf, "cGxlYXN1cmUu");
|
||||
StrBase64Str(aBuf, sizeof(aBuf), "leasure.");
|
||||
EXPECT_STREQ(aBuf, "bGVhc3VyZS4=");
|
||||
StrBase64Str(aBuf, sizeof(aBuf), "easure.");
|
||||
EXPECT_STREQ(aBuf, "ZWFzdXJlLg==");
|
||||
StrBase64Str(aBuf, sizeof(aBuf), "asure.");
|
||||
EXPECT_STREQ(aBuf, "YXN1cmUu");
|
||||
StrBase64Str(aBuf, sizeof(aBuf), "sure.");
|
||||
EXPECT_STREQ(aBuf, "c3VyZS4=");
|
||||
}
|
||||
|
||||
TEST(Str, Base64Decode)
|
||||
{
|
||||
char aOut[17];
|
||||
str_copy(aOut, "XXXXXXXXXXXXXXXX", sizeof(aOut));
|
||||
EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), ""), 0);
|
||||
EXPECT_STREQ(aOut, "XXXXXXXXXXXXXXXX");
|
||||
|
||||
// https://en.wikipedia.org/w/index.php?title=Base64&oldid=1033503483#Output_padding
|
||||
str_copy(aOut, "XXXXXXXXXXXXXXXX", sizeof(aOut));
|
||||
EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "cGxlYXN1cmUu"), 9);
|
||||
EXPECT_STREQ(aOut, "pleasure.XXXXXXX");
|
||||
str_copy(aOut, "XXXXXXXXXXXXXXXX", sizeof(aOut));
|
||||
EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "bGVhc3VyZS4="), 8);
|
||||
EXPECT_STREQ(aOut, "leasure.XXXXXXXX");
|
||||
str_copy(aOut, "XXXXXXXXXXXXXXXX", sizeof(aOut));
|
||||
EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "ZWFzdXJlLg=="), 7);
|
||||
EXPECT_STREQ(aOut, "easure.XXXXXXXXX");
|
||||
str_copy(aOut, "XXXXXXXXXXXXXXXX", sizeof(aOut));
|
||||
EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "YXN1cmUu"), 6);
|
||||
EXPECT_STREQ(aOut, "asure.XXXXXXXXXX");
|
||||
str_copy(aOut, "XXXXXXXXXXXXXXXX", sizeof(aOut));
|
||||
EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "c3VyZS4="), 5);
|
||||
EXPECT_STREQ(aOut, "sure.XXXXXXXXXXX");
|
||||
str_copy(aOut, "XXXXXXXXXXXXXXXX", sizeof(aOut));
|
||||
EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "////"), 3);
|
||||
EXPECT_STREQ(aOut, "\xff\xff\xffXXXXXXXXXXXXX");
|
||||
}
|
||||
|
||||
TEST(Str, Base64DecodeError)
|
||||
{
|
||||
char aBuf[128];
|
||||
// Wrong padding.
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "A"), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AA"), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAA"), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "A==="), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "=AAA"), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "===="), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAA=AAAA"), 0);
|
||||
// Invalid characters.
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "----"), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAAA "), 0);
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAA "), 0);
|
||||
// Invalid padding values.
|
||||
EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "//=="), 0);
|
||||
}
|
||||
|
||||
TEST(Str, Tokenize)
|
||||
{
|
||||
char aTest[] = "GER,RUS,ZAF,BRA,CAN";
|
||||
|
|
Loading…
Reference in a new issue