Tests: add live integration test (optional via PRIVATEBIN_IT), fix WinHTTP host/port + TLS opts, robust JSON parser (meta.time_to_live), CTest wiring; Add LLVM/clang-cl coverage option and docs; add build_thinkpad.bat; README updates

This commit is contained in:
mbusc
2025-08-28 15:22:00 +02:00
parent 7a125a4c9c
commit 0f58d40f52
10 changed files with 467 additions and 39 deletions

View File

@ -1,11 +1,30 @@
#include "http_client.h"
#include <iostream>
#include <string>
#include <sstream>
#ifdef WINDOWS
#include <windows.h>
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
static std::string last_winhttp_error(const char* where) {
DWORD err = GetLastError();
LPVOID lpMsgBuf = nullptr;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&lpMsgBuf,
0, NULL);
std::ostringstream os;
os << "[WinHTTP] " << where << " failed, error=" << err;
if (lpMsgBuf) {
os << ": " << (char*)lpMsgBuf;
LocalFree(lpMsgBuf);
}
return os.str();
}
static std::wstring utf8_to_wide(const std::string& s) {
if (s.empty()) return std::wstring();
@ -56,14 +75,28 @@ bool HttpClient::get(const std::string& url, std::string& response) {
WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) {
std::cerr << last_winhttp_error("WinHttpOpen(GET)") << std::endl;
return false;
}
// Force modern TLS versions to avoid handshake failures on some hosts
DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000 /* TLS1_3 if available */;
WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof(protocols));
// Disable HTTP/2 if it causes issues
#ifdef WINHTTP_DISABLE_FEATURE_HTTP2
DWORD features = WINHTTP_DISABLE_FEATURE_HTTP2;
WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features, sizeof(features));
#endif
// Specify an HTTP server
HINTERNET hConnect = WinHttpConnect(hSession, urlComp.lpszHostName,
urlComp.nPort, 0);
// Specify an HTTP server (host must be null-terminated; urlComp provides length)
std::wstring host = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0)
? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength)
: std::wstring();
INTERNET_PORT port = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT);
HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), port, 0);
if (!hConnect) {
std::cerr << last_winhttp_error("WinHttpConnect(GET)") << std::endl;
WinHttpCloseHandle(hSession);
return false;
}
@ -86,7 +119,9 @@ bool HttpClient::get(const std::string& url, std::string& response) {
WINHTTP_FLAG_SECURE : 0);
// Set headers per API requirement
LPCWSTR headers = L"X-Requested-With: JSONHttpRequest\r\nAccept: application/json";
WinHttpAddRequestHeaders(hRequest, headers, -1L, WINHTTP_ADDREQ_FLAG_ADD);
if (!WinHttpAddRequestHeaders(hRequest, headers, -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(GET)") << std::endl;
}
if (!hRequest) {
WinHttpCloseHandle(hConnect);
@ -99,6 +134,7 @@ bool HttpClient::get(const std::string& url, std::string& response) {
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
0, 0)) {
std::cerr << last_winhttp_error("WinHttpSendRequest(GET)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -107,6 +143,7 @@ bool HttpClient::get(const std::string& url, std::string& response) {
// End the request
if (!WinHttpReceiveResponse(hRequest, NULL)) {
std::cerr << last_winhttp_error("WinHttpReceiveResponse(GET)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -122,6 +159,7 @@ bool HttpClient::get(const std::string& url, std::string& response) {
// Check for available data
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(GET)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -140,6 +178,7 @@ bool HttpClient::get(const std::string& url, std::string& response) {
// Read the data
ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
std::cerr << last_winhttp_error("WinHttpReadData(GET)") << std::endl;
delete[] pszOutBuffer;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
@ -189,14 +228,26 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) {
std::cerr << last_winhttp_error("WinHttpOpen(POST)") << std::endl;
return false;
}
DWORD protocols2 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000;
WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols2, sizeof(protocols2));
#ifdef WINHTTP_DISABLE_FEATURE_HTTP2
DWORD features2 = WINHTTP_DISABLE_FEATURE_HTTP2;
WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features2, sizeof(features2));
#endif
// Specify an HTTP server
HINTERNET hConnect = WinHttpConnect(hSession, urlComp.lpszHostName,
urlComp.nPort, 0);
// Specify an HTTP server (ensure host is null-terminated)
std::wstring host2 = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0)
? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength)
: std::wstring();
INTERNET_PORT port2 = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT);
HINTERNET hConnect = WinHttpConnect(hSession, host2.c_str(), port2, 0);
if (!hConnect) {
std::cerr << last_winhttp_error("WinHttpConnect(POST)") << std::endl;
WinHttpCloseHandle(hSession);
return false;
}
@ -211,6 +262,7 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
WINHTTP_FLAG_SECURE : 0);
if (!hRequest) {
std::cerr << "[WinHTTP] WinHttpOpenRequest(POST) failed" << std::endl;
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return false;
@ -224,6 +276,7 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
WINHTTP_ADDREQ_FLAG_ADD);
if (!bResults) {
std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(POST)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -236,6 +289,7 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
(LPVOID)data.c_str(), (DWORD)data.length(),
(DWORD)data.length(), 0)) {
std::cerr << last_winhttp_error("WinHttpSendRequest(POST)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -244,6 +298,7 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
// End the request
if (!WinHttpReceiveResponse(hRequest, NULL)) {
std::cerr << last_winhttp_error("WinHttpReceiveResponse(POST)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -259,6 +314,7 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
// Check for available data
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(POST)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -277,6 +333,7 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
// Read the data
ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
std::cerr << last_winhttp_error("WinHttpReadData(POST)") << std::endl;
delete[] pszOutBuffer;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
@ -326,14 +383,26 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) {
std::cerr << last_winhttp_error("WinHttpOpen(DEL)") << std::endl;
return false;
}
DWORD protocols3 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000;
WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols3, sizeof(protocols3));
#ifdef WINHTTP_DISABLE_FEATURE_HTTP2
DWORD features3 = WINHTTP_DISABLE_FEATURE_HTTP2;
WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features3, sizeof(features3));
#endif
// Specify an HTTP server
HINTERNET hConnect = WinHttpConnect(hSession, urlComp.lpszHostName,
urlComp.nPort, 0);
// Specify an HTTP server (ensure host is null-terminated)
std::wstring host3 = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0)
? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength)
: std::wstring();
INTERNET_PORT port3 = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT);
HINTERNET hConnect = WinHttpConnect(hSession, host3.c_str(), port3, 0);
if (!hConnect) {
std::cerr << last_winhttp_error("WinHttpConnect(DEL)") << std::endl;
WinHttpCloseHandle(hSession);
return false;
}
@ -348,6 +417,7 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
WINHTTP_FLAG_SECURE : 0);
if (!hRequest) {
std::cerr << "[WinHTTP] WinHttpOpenRequest(DEL) failed" << std::endl;
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return false;
@ -361,6 +431,7 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
WINHTTP_ADDREQ_FLAG_ADD);
if (!bResults) {
std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(DEL)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -372,6 +443,7 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
(LPVOID)data.c_str(), (DWORD)data.length(),
(DWORD)data.length(), 0)) {
std::cerr << last_winhttp_error("WinHttpSendRequest(DEL)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -380,6 +452,7 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
// End the request
if (!WinHttpReceiveResponse(hRequest, NULL)) {
std::cerr << last_winhttp_error("WinHttpReceiveResponse(DEL)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -395,6 +468,7 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
// Check for available data
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(DEL)") << std::endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
@ -413,6 +487,7 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
// Read the data
ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
std::cerr << last_winhttp_error("WinHttpReadData(DEL)") << std::endl;
delete[] pszOutBuffer;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);

View File

@ -105,8 +105,18 @@ bool JsonParser::parse_paste_json(const json& json_data,
}
}
// Extract expiration
expiration = json_data.at("meta").at("expire");
// Extract expiration; servers may return either meta.expire (string)
// or meta.time_to_live (integer seconds). Both are optional for our use.
try {
expiration = json_data.at("meta").at("expire");
} catch (...) {
try {
auto ttl = json_data.at("meta").at("time_to_live").get<int>();
expiration = std::to_string(ttl);
} catch (...) {
expiration.clear();
}
}
return true;
} catch (...) {