This commit is contained in:
2025-12-03 01:08:36 +05:30
parent bd9d75e2a2
commit 4af30facaf
5 changed files with 166 additions and 19 deletions

View File

@ -65,6 +65,6 @@ endif()
# -------------------------------------------------
# Local Development Sandboxes (not included in source control)
# -------------------------------------------------
if (EXISTS ".local")
add_subdirectory(.local)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.local")
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/.local")
endif()

View File

@ -21,6 +21,52 @@
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)
{
z_stream zs{};
@ -28,6 +74,7 @@ namespace IACore
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
// 15 + 32 = Auto-detect Gzip or Zlib
if (inflateInit2(&zs, 15 + 32) != Z_OK)
return MakeUnexpected("Failed to initialize zlib inflate");
@ -35,18 +82,28 @@ namespace IACore
zs.avail_in = static_cast<uInt>(data.size());
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;
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);
zs.avail_out = static_cast<uInt>(outBuffer.size() - zs.total_out);
size_t newSize = outBuffer.size() * 2;
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);
@ -55,9 +112,10 @@ namespace IACore
inflateEnd(&zs);
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);
return outBuffer;
}
@ -165,4 +223,46 @@ namespace IACore
outBuffer.resize(compressedSize);
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

View File

@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/HttpClient.hpp>
#include <IACore/DataOps.hpp>
#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,
IN PCCHAR defaultContentType)
{
auto httpHeaders = BuildHeaders(headers, defaultContentType);
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)
{
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return res->body;
return PreprocessResponse(res->body);
else
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
}
@ -81,14 +110,17 @@ namespace IACore
httpHeaders.erase(t);
}
static_cast<httplib::Client *>(m_client)->set_keep_alive(true);
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)
{
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return res->body;
return PreprocessResponse(res->body);
else
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
}

View File

@ -22,11 +22,27 @@ namespace IACore
{
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> ZlibDeflate(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);
};
}
} // namespace IACore

View File

@ -135,12 +135,8 @@ namespace IACore
~HttpClient();
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,
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,
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
@ -169,6 +165,9 @@ namespace IACore
private:
PVOID m_client{};
EResponseCode m_lastResponseCode;
private:
String PreprocessResponse(IN CONST String& response);
};
template<typename _response_type>