Fixes
This commit is contained in:
@ -65,6 +65,6 @@ endif()
|
|||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
# Local Development Sandboxes (not included in source control)
|
# Local Development Sandboxes (not included in source control)
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
if (EXISTS ".local")
|
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.local")
|
||||||
add_subdirectory(.local)
|
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/.local")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@ -21,6 +21,52 @@
|
|||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
|
// FNV-1a 32-bit Constants
|
||||||
|
constexpr uint32_t FNV1A_32_PRIME = 0x01000193; // 16777619
|
||||||
|
constexpr uint32_t FNV1A_32_OFFSET = 0x811c9dc5; // 2166136261
|
||||||
|
|
||||||
|
UINT32 DataOps::Hash(IN CONST String &string)
|
||||||
|
{
|
||||||
|
uint32_t hash = FNV1A_32_OFFSET;
|
||||||
|
for (char c : string)
|
||||||
|
{
|
||||||
|
hash ^= static_cast<uint8_t>(c);
|
||||||
|
hash *= FNV1A_32_PRIME;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 DataOps::Hash(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
uint32_t hash = FNV1A_32_OFFSET;
|
||||||
|
const uint8_t *ptr = static_cast<const uint8_t *>(data.data());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < data.size(); ++i)
|
||||||
|
{
|
||||||
|
hash ^= ptr[i];
|
||||||
|
hash *= FNV1A_32_PRIME;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataOps::CompressionType DataOps::DetectCompression(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
if (data.size() < 2)
|
||||||
|
return CompressionType::None;
|
||||||
|
|
||||||
|
// Check for GZIP Magic Number (0x1F 0x8B)
|
||||||
|
if (data[0] == 0x1F && data[1] == 0x8B)
|
||||||
|
return CompressionType::Gzip;
|
||||||
|
|
||||||
|
// Check for ZLIB Magic Number (starts with 0x78)
|
||||||
|
// 0x78 = Deflate compression with 32k window size
|
||||||
|
// Valid second bytes: 0x01 (Fastest), 0x9C (Default), 0xDA (Best)
|
||||||
|
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
|
||||||
|
return CompressionType::Zlib;
|
||||||
|
|
||||||
|
return CompressionType::None;
|
||||||
|
}
|
||||||
|
|
||||||
Expected<Vector<UINT8>, String> DataOps::ZlibInflate(IN Span<CONST UINT8> data)
|
Expected<Vector<UINT8>, String> DataOps::ZlibInflate(IN Span<CONST UINT8> data)
|
||||||
{
|
{
|
||||||
z_stream zs{};
|
z_stream zs{};
|
||||||
@ -28,6 +74,7 @@ namespace IACore
|
|||||||
zs.zfree = Z_NULL;
|
zs.zfree = Z_NULL;
|
||||||
zs.opaque = Z_NULL;
|
zs.opaque = Z_NULL;
|
||||||
|
|
||||||
|
// 15 + 32 = Auto-detect Gzip or Zlib
|
||||||
if (inflateInit2(&zs, 15 + 32) != Z_OK)
|
if (inflateInit2(&zs, 15 + 32) != Z_OK)
|
||||||
return MakeUnexpected("Failed to initialize zlib inflate");
|
return MakeUnexpected("Failed to initialize zlib inflate");
|
||||||
|
|
||||||
@ -35,18 +82,28 @@ namespace IACore
|
|||||||
zs.avail_in = static_cast<uInt>(data.size());
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
Vector<UINT8> outBuffer;
|
Vector<UINT8> outBuffer;
|
||||||
outBuffer.resize(data.size() * 2);
|
// Start with 2x input size.
|
||||||
|
// Small packets compress well, so maybe 4x for very small inputs.
|
||||||
|
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||||
|
outBuffer.resize(guessSize);
|
||||||
|
|
||||||
|
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||||
|
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (zs.total_out >= outBuffer.size())
|
if (zs.avail_out == 0)
|
||||||
{
|
{
|
||||||
outBuffer.resize(outBuffer.size() * 2);
|
size_t currentPos = zs.total_out;
|
||||||
}
|
|
||||||
|
|
||||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + zs.total_out);
|
size_t newSize = outBuffer.size() * 2;
|
||||||
zs.avail_out = static_cast<uInt>(outBuffer.size() - zs.total_out);
|
outBuffer.resize(newSize);
|
||||||
|
|
||||||
|
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + currentPos);
|
||||||
|
|
||||||
|
zs.avail_out = static_cast<uInt>(newSize - currentPos);
|
||||||
|
}
|
||||||
|
|
||||||
ret = inflate(&zs, Z_NO_FLUSH);
|
ret = inflate(&zs, Z_NO_FLUSH);
|
||||||
|
|
||||||
@ -55,9 +112,10 @@ namespace IACore
|
|||||||
inflateEnd(&zs);
|
inflateEnd(&zs);
|
||||||
|
|
||||||
if (ret != Z_STREAM_END)
|
if (ret != Z_STREAM_END)
|
||||||
return MakeUnexpected("Failed to inflate, corrupt or incomplete data");
|
return MakeUnexpected("Failed to inflate: corrupt data or stream error");
|
||||||
|
|
||||||
outBuffer.resize(zs.total_out);
|
outBuffer.resize(zs.total_out);
|
||||||
|
|
||||||
return outBuffer;
|
return outBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,4 +223,46 @@ namespace IACore
|
|||||||
outBuffer.resize(compressedSize);
|
outBuffer.resize(compressedSize);
|
||||||
return outBuffer;
|
return outBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::GZipDeflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
z_stream zs{};
|
||||||
|
zs.zalloc = Z_NULL;
|
||||||
|
zs.zfree = Z_NULL;
|
||||||
|
zs.opaque = Z_NULL;
|
||||||
|
|
||||||
|
// WindowBits = 15 + 16 (31) -> This forces GZIP encoding
|
||||||
|
// MemLevel = 8 (default)
|
||||||
|
// Strategy = Z_DEFAULT_STRATEGY
|
||||||
|
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||||
|
return MakeUnexpected("Failed to initialize gzip deflate");
|
||||||
|
|
||||||
|
zs.next_in = const_cast<Bytef *>(data.data());
|
||||||
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
|
Vector<UINT8> outBuffer;
|
||||||
|
|
||||||
|
outBuffer.resize(deflateBound(&zs, data.size()) + 1024); // Additional 1KB buffer for safety
|
||||||
|
|
||||||
|
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||||
|
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||||
|
|
||||||
|
int ret = deflate(&zs, Z_FINISH);
|
||||||
|
|
||||||
|
if (ret != Z_STREAM_END)
|
||||||
|
{
|
||||||
|
deflateEnd(&zs);
|
||||||
|
return MakeUnexpected("Failed to deflate");
|
||||||
|
}
|
||||||
|
|
||||||
|
outBuffer.resize(zs.total_out);
|
||||||
|
|
||||||
|
deflateEnd(&zs);
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::GZipInflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
return ZlibInflate(data);
|
||||||
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -15,6 +15,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/HttpClient.hpp>
|
#include <IACore/HttpClient.hpp>
|
||||||
|
#include <IACore/DataOps.hpp>
|
||||||
|
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
|
|
||||||
@ -48,19 +49,47 @@ namespace IACore
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String HttpClient::PreprocessResponse(IN CONST String &response)
|
||||||
|
{
|
||||||
|
const auto responseBytes = Span<CONST UINT8>{(PCUINT8) response.data(), response.size()};
|
||||||
|
const auto compression = DataOps::DetectCompression(responseBytes);
|
||||||
|
switch (compression)
|
||||||
|
{
|
||||||
|
case DataOps::CompressionType::Gzip: {
|
||||||
|
const auto data = DataOps::GZipInflate(responseBytes);
|
||||||
|
if (!data)
|
||||||
|
return response;
|
||||||
|
return String((PCCHAR) data->data(), data->size());
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataOps::CompressionType::Zlib: {
|
||||||
|
const auto data = DataOps::ZlibInflate(responseBytes);
|
||||||
|
if (!data)
|
||||||
|
return response;
|
||||||
|
return String((PCCHAR) data->data(), data->size());
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataOps::CompressionType::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
Expected<String, String> HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
Expected<String, String> HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
||||||
IN PCCHAR defaultContentType)
|
IN PCCHAR defaultContentType)
|
||||||
{
|
{
|
||||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||||
|
|
||||||
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
|
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
|
||||||
auto res = static_cast<httplib::Client *>(m_client)->Get(path.c_str(), httpHeaders);
|
auto res = static_cast<httplib::Client *>(m_client)->Get(
|
||||||
|
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders);
|
||||||
|
|
||||||
if (res)
|
if (res)
|
||||||
{
|
{
|
||||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||||
if (res->status >= 200 && res->status < 300)
|
if (res->status >= 200 && res->status < 300)
|
||||||
return res->body;
|
return PreprocessResponse(res->body);
|
||||||
else
|
else
|
||||||
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
||||||
}
|
}
|
||||||
@ -81,14 +110,17 @@ namespace IACore
|
|||||||
httpHeaders.erase(t);
|
httpHeaders.erase(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static_cast<httplib::Client *>(m_client)->set_keep_alive(true);
|
||||||
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
|
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
|
||||||
auto res = static_cast<httplib::Client *>(m_client)->Post(path.c_str(), httpHeaders, body, contentType.c_str());
|
auto res = static_cast<httplib::Client *>(m_client)->Post(
|
||||||
|
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders, body,
|
||||||
|
contentType.c_str());
|
||||||
|
|
||||||
if (res)
|
if (res)
|
||||||
{
|
{
|
||||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||||
if (res->status >= 200 && res->status < 300)
|
if (res->status >= 200 && res->status < 300)
|
||||||
return res->body;
|
return PreprocessResponse(res->body);
|
||||||
else
|
else
|
||||||
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,10 +23,26 @@ namespace IACore
|
|||||||
class DataOps
|
class DataOps
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
enum class CompressionType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Gzip,
|
||||||
|
Zlib
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
STATIC UINT32 Hash(IN CONST String &string);
|
||||||
|
STATIC UINT32 Hash(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
STATIC CompressionType DetectCompression(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
STATIC Expected<Vector<UINT8>, String> GZipInflate(IN Span<CONST UINT8> data);
|
||||||
|
STATIC Expected<Vector<UINT8>, String> GZipDeflate(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
STATIC Expected<Vector<UINT8>, String> ZlibInflate(IN Span<CONST UINT8> data);
|
STATIC Expected<Vector<UINT8>, String> ZlibInflate(IN Span<CONST UINT8> data);
|
||||||
STATIC Expected<Vector<UINT8>, String> ZlibDeflate(IN Span<CONST UINT8> data);
|
STATIC Expected<Vector<UINT8>, String> ZlibDeflate(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
STATIC Expected<Vector<UINT8>, String> ZstdInflate(IN Span<CONST UINT8> data);
|
STATIC Expected<Vector<UINT8>, String> ZstdInflate(IN Span<CONST UINT8> data);
|
||||||
STATIC Expected<Vector<UINT8>, String> ZstdDeflate(IN Span<CONST UINT8> data);
|
STATIC Expected<Vector<UINT8>, String> ZstdDeflate(IN Span<CONST UINT8> data);
|
||||||
};
|
};
|
||||||
}
|
} // namespace IACore
|
||||||
@ -135,12 +135,8 @@ namespace IACore
|
|||||||
~HttpClient();
|
~HttpClient();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Automatically adds the following headers, if not present:
|
|
||||||
// 1) EHeaderType::CONTENT_TYPE = defaultContentType
|
|
||||||
Expected<String, String> RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
Expected<String, String> RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
||||||
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
||||||
// Automatically adds the following headers, if not present:
|
|
||||||
// 1) EHeaderType::CONTENT_TYPE = defaultContentType
|
|
||||||
Expected<String, String> RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
|
Expected<String, String> RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
|
||||||
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
@ -169,6 +165,9 @@ namespace IACore
|
|||||||
private:
|
private:
|
||||||
PVOID m_client{};
|
PVOID m_client{};
|
||||||
EResponseCode m_lastResponseCode;
|
EResponseCode m_lastResponseCode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
String PreprocessResponse(IN CONST String& response);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename _response_type>
|
template<typename _response_type>
|
||||||
|
|||||||
Reference in New Issue
Block a user