Fixes
This commit is contained in:
@ -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()
|
||||
|
||||
@ -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
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -23,10 +23,26 @@ namespace IACore
|
||||
class DataOps
|
||||
{
|
||||
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
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user