Files
LNXRNT/Sources/httprequest.cpp
2026-02-20 23:40:15 -08:00

2387 lines
96 KiB
C++

#ifdef WITH_NETWORKING
#include "httprequest.h"
#include "connection_pool.h"
#include "global_thread_pool.h"
#include <kinc/log.h>
#include <string.h>
#include <stdlib.h>
#include <atomic>
#include <regex>
#include <chrono>
#include <sstream>
#include <algorithm>
#include <fstream>
#ifdef _WIN32
#define NOMINMAX
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#define SOCKET int
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket close
#endif
#ifdef WITH_SSL
#ifdef _WIN32
#define SECURITY_WIN32
#include <wincrypt.h>
#include <schannel.h>
#include <security.h>
#include <sspi.h>
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "secur32.lib")
#else
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#endif
#endif
using namespace v8;
namespace HttpRequestWrapper {
// timeout helper functions for SSL/socket operations
static int receiveWithTimeout(SOCKET socket, char* buffer, int buffer_size, int timeout_ms) {
// non-blocking
#ifdef _WIN32
u_long mode = 1;
if (ioctlsocket(socket, FIONBIO, &mode) != 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set socket to non-blocking mode: %d", WSAGetLastError());
#endif
return -1;
}
#else
int flags = fcntl(socket, F_GETFL, 0);
if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) < 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set socket to non-blocking mode: %s", strerror(errno));
#endif
return -1;
}
#endif
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(socket, &read_fds);
struct timeval timeout;
timeout.tv_sec = timeout_ms / 1000;
timeout.tv_usec = (timeout_ms % 1000) * 1000;
#ifdef _WIN32
int select_result = select(0, &read_fds, NULL, NULL, &timeout);
#else
int select_result = select(socket + 1, &read_fds, NULL, NULL, &timeout);
#endif
if (select_result > 0 && FD_ISSET(socket, &read_fds)) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket has data available, attempting recv...");
#endif
int received = ::recv(socket, buffer, buffer_size, 0);
// restore blocking mode
#ifdef _WIN32
mode = 0;
ioctlsocket(socket, FIONBIO, &mode);
#else
// restore original flags
fcntl(socket, F_SETFL, flags);
#endif
if (received > 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Received %d bytes with timeout handling", received);
#endif
return received;
} else if (received == 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Connection closed gracefully by server");
#endif
return 0;
} else {
#ifdef _WIN32
int error = WSAGetLastError();
if (error == WSAEWOULDBLOCK) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "No data available (WSAEWOULDBLOCK)");
#endif
return 0;
} else {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Receive error: %d", error);
#endif
return -1;
}
#else
if (errno == EAGAIN || errno == EWOULDBLOCK) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "No data available (EAGAIN/EWOULDBLOCK)");
#endif
return 0;
} else {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Receive error: %s", strerror(errno));
#endif
return -1;
}
#endif
}
} else if (select_result == 0) {
// timeouts are expected
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket receive timeout after %d ms - no data available", timeout_ms);
#endif
// restore
#ifdef _WIN32
mode = 0;
ioctlsocket(socket, FIONBIO, &mode);
#else
fcntl(socket, F_SETFL, flags);
#endif
return 0;
} else {
#ifdef _WIN32
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Select error: %d", WSAGetLastError());
#endif
#else
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Select error: %s", strerror(errno));
#endif
#endif
#ifdef _WIN32
mode = 0;
ioctlsocket(socket, FIONBIO, &mode);
#else
fcntl(socket, F_SETFL, flags);
#endif
return -1;
}
}
static std::unordered_map<int, std::unique_ptr<HttpRequestClient>> active_requests;
static std::atomic<int> next_request_id{1};
static std::atomic<bool> initialized{false};
void initialize() {
if (!initialized.exchange(true)) {
#ifdef _WIN32
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "WSAStartup failed: %d", result);
#endif
} else {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Initializing native HTTP request support");
#endif
}
#else
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Initializing native HTTP request support");
#endif
#endif
}
}
void cleanup() {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Starting HTTP request cleanup, waiting for active operations...");
#endif
bool has_active_operations = true;
int wait_count = 0;
const int max_wait_ms = 5000;
while (has_active_operations && wait_count < max_wait_ms) {
has_active_operations = false;
for (const auto& pair : active_requests) {
if (pair.second && pair.second->active_operation_.load()) {
has_active_operations = true;
break;
}
}
if (has_active_operations) {
#ifdef _WIN32
Sleep(10);
#else
usleep(10000);
#endif
wait_count += 10;
}
}
if (wait_count >= max_wait_ms) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_WARNING, "HTTP request cleanup timed out after %d ms, forcing cleanup", max_wait_ms);
#endif
} else {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "All active operations completed after %d ms", wait_count);
#endif
}
active_requests.clear();
if (initialized.exchange(false)) {
#ifdef _WIN32
WSACleanup();
#endif
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request cleanup complete");
#endif
}
}
void processEvents() {
// called from main thread
for (auto& pair : active_requests) {
if (pair.second) {
pair.second->processEvents();
}
}
}
int createHttpRequest(v8::Isolate* isolate) {
int id = next_request_id++;
auto client = std::make_unique<HttpRequestClient>(isolate, getGlobalContext());
active_requests[id] = std::move(client);
return id;
}
HttpRequestClient::HttpRequestClient(Isolate* isolate, Global<Context>* global_context)
: isolate_(isolate), global_context_(global_context), ready_state_(UNSENT),
response_type_(DEFAULT), async_(true), timeout_(0), with_credentials_(false),
done_event_fired_(false), active_operation_(false)
#ifdef WITH_SSL
#ifdef _WIN32
, ssl_cred_handle_(nullptr), ssl_context_handle_(nullptr), ssl_context_initialized_(false), ssl_socket_(-1)
#else
, ssl_ctx_(nullptr), ssl_(nullptr), ssl_initialized_(false)
#endif
#endif
{
#ifdef WITH_SSL
#ifdef _WIN32
// SSL handles to zero
memset(&ssl_cred_handle_, 0, sizeof(ssl_cred_handle_));
memset(&ssl_context_handle_, 0, sizeof(ssl_context_handle_));
#endif
#endif
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Creating HTTP request client");
#endif
}
HttpRequestClient::~HttpRequestClient() {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Destroying HTTP request client");
#endif
#ifdef WITH_SSL
cleanupSSL();
#endif
}
void HttpRequestClient::open(const std::string& method, const std::string& url, bool async) {
std::lock_guard<std::mutex> lock(state_mutex_);
method_ = method;
url_ = url;
async_ = async;
response_ = HttpResponse();
setReadyState(OPENED);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request opened: %s %s", method.c_str(), url.c_str());
#endif
}
void HttpRequestClient::setRequestHeader(const std::string& header, const std::string& value) {
std::lock_guard<std::mutex> lock(state_mutex_);
if (ready_state_ != OPENED) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Cannot set request header: request not opened");
#endif
return;
}
request_headers_[header] = value;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Set request header: %s = %s", header.c_str(), value.c_str());
#endif
}
void HttpRequestClient::send(const std::string& data) {
std::lock_guard<std::mutex> lock(state_mutex_);
if (ready_state_ != OPENED) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Cannot send: request not opened");
#endif
return;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Sending HTTP request with data length: %zu", data.length());
#endif
request_data_ = data;
if (async_) {
// use thread pool
GlobalThreadPool::getInstance().enqueue([this]() {
this->performRequest();
});
} else {
// synchronously
performRequest();
}
}
void HttpRequestClient::abort() {
std::lock_guard<std::mutex> lock(state_mutex_);
if (ready_state_ == DONE) {
return;
}
setReadyState(DONE);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request aborted");
#endif
if (!on_abort_.IsEmpty()) {
callCallback(on_abort_, 0, nullptr);
}
}
std::string HttpRequestClient::getAllResponseHeaders() {
std::lock_guard<std::mutex> lock(state_mutex_);
std::ostringstream headers;
for (const auto& header : response_.headers) {
headers << header.first << ": " << header.second << "\r\n";
}
return headers.str();
}
std::string HttpRequestClient::getResponseHeader(const std::string& header) {
std::lock_guard<std::mutex> lock(state_mutex_);
std::string lower_header = header;
std::transform(lower_header.begin(), lower_header.end(), lower_header.begin(), ::tolower);
for (const auto& h : response_.headers) {
std::string lower_h = h.first;
std::transform(lower_h.begin(), lower_h.end(), lower_h.begin(), ::tolower);
if (lower_h == lower_header) {
return h.second;
}
}
return "";
}
void HttpRequestClient::setOnReadyStateChange(Local<Function> callback) {
on_ready_state_change_.Reset(isolate_, callback);
}
void HttpRequestClient::setOnLoad(Local<Function> callback) {
on_load_.Reset(isolate_, callback);
}
void HttpRequestClient::setOnError(Local<Function> callback) {
on_error_.Reset(isolate_, callback);
}
void HttpRequestClient::setOnTimeout(Local<Function> callback) {
on_timeout_.Reset(isolate_, callback);
}
void HttpRequestClient::setOnAbort(Local<Function> callback) {
on_abort_.Reset(isolate_, callback);
}
void HttpRequestClient::setOnProgress(Local<Function> callback) {
on_progress_.Reset(isolate_, callback);
}
void HttpRequestClient::setOnLoadStart(Local<Function> callback) {
on_load_start_.Reset(isolate_, callback);
}
void HttpRequestClient::setOnLoadEnd(Local<Function> callback) {
on_load_end_.Reset(isolate_, callback);
}
void HttpRequestClient::performRequest() {
active_operation_ = true;
handleLoadStart();
try {
std::string host, path;
int port;
bool is_https;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP DEBUG: Starting request to URL: %s", url_.c_str());
#endif
if (!parseUrl(url_, host, port, path, is_https)) {
response_.error = "Invalid URL";
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "HTTP DEBUG: Failed to parse URL: %s", url_.c_str());
#endif
setReadyState(DONE);
handleError(response_.error);
active_operation_ = false;
return;
}
// check if its a local file request
if (port == 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Loading local file: %s", path.c_str());
#endif
loadLocalFile(path);
active_operation_ = false;
return;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Parsed URL - host: %s, port: %d, path: %s, https: %s",
host.c_str(), port, path.c_str(), is_https ? "true" : "false");
#endif
#ifdef WITH_SSL
if (is_https) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Using HTTPS/SSL for request");
#endif
}
#else
if (is_https) {
response_.error = "HTTPS not supported - build with WITH_SSL=1";
setReadyState(DONE);
handleError(response_.error);
active_operation_ = false;
return;
}
#endif
// use connection pool
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Acquiring connection for HTTP request");
#endif
SOCKET sock = g_connection_pool.acquire_connection(host, port, is_https);
if (sock == INVALID_SOCKET) {
response_.error = "Failed to acquire connection";
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Connection acquisition failed");
#endif
setReadyState(DONE);
handleError(response_.error);
active_operation_ = false;
return;
}
// set timeout when specified
if (timeout_ > 0) {
#ifdef _WIN32
DWORD timeout_ms = timeout_;
::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_ms, sizeof(timeout_ms));
::setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout_ms, sizeof(timeout_ms));
#else
struct timeval timeout;
timeout.tv_sec = timeout_ / 1000;
timeout.tv_usec = (timeout_ % 1000) * 1000;
::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
::setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
#endif
}
kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", host.c_str(), port);
#ifdef WITH_SSL
if (is_https) {
kinc_log(KINC_LOG_LEVEL_INFO, "Performing SSL handshake");
ssl_socket_ = static_cast<int>(sock);
if (!performSSLHandshake(static_cast<int>(sock), host)) {
response_.error = "SSL handshake failed";
closesocket(sock);
setReadyState(DONE);
handleError(response_.error);
active_operation_ = false;
return;
}
kinc_log(KINC_LOG_LEVEL_INFO, "SSL handshake completed successfully");
}
#endif
std::string request_data = formatHttpRequest(method_, path, host, request_data_);
kinc_log(KINC_LOG_LEVEL_INFO, "Sending HTTP request (%zu bytes):", request_data.length());
kinc_log(KINC_LOG_LEVEL_INFO, "Request preview: %.200s", request_data.c_str());
int bytes_sent = 0;
#ifdef WITH_SSL
if (is_https) {
bytes_sent = sslWrite(request_data.c_str(), static_cast<int>(request_data.length()));
} else {
bytes_sent = ::send(sock, request_data.c_str(), static_cast<int>(request_data.length()), 0);
}
#else
bytes_sent = ::send(sock, request_data.c_str(), static_cast<int>(request_data.length()), 0);
#endif
if (bytes_sent == SOCKET_ERROR || bytes_sent == 0) {
response_.error = "Failed to send request";
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send HTTP request");
setReadyState(DONE);
handleError(response_.error);
active_operation_ = false;
return;
}
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request sent successfully");
char buffer[4096];
int bytes_received;
std::string response_data;
int total_bytes = 0;
kinc_log(KINC_LOG_LEVEL_INFO, "Starting to receive HTTP response");
while (true) {
#ifdef WITH_SSL
if (is_https) {
kinc_log(KINC_LOG_LEVEL_INFO, "Calling sslRead for %zu bytes", sizeof(buffer) - 1);
bytes_received = sslRead(buffer, sizeof(buffer) - 1);
kinc_log(KINC_LOG_LEVEL_INFO, "sslRead returned %d bytes", bytes_received);
} else {
bytes_received = ::recv(sock, buffer, sizeof(buffer) - 1, 0);
}
#else
bytes_received = ::recv(sock, buffer, sizeof(buffer) - 1, 0);
#endif
if (bytes_received <= 0) {
kinc_log(KINC_LOG_LEVEL_INFO, "Breaking from response reading loop, bytes_received: %d", bytes_received);
break;
}
buffer[bytes_received] = '\0';
response_data += buffer;
total_bytes += bytes_received;
kinc_log(KINC_LOG_LEVEL_INFO, "Received %d bytes (total: %d)", bytes_received, total_bytes);
}
kinc_log(KINC_LOG_LEVEL_INFO, "Finished receiving data, total bytes: %d", total_bytes);
#ifdef WITH_SSL
if (is_https) {
cleanupSSL();
}
#endif
// return connection to pool for reuse instead of closing
g_connection_pool.release_connection(host, port, sock, is_https);
if (bytes_received == SOCKET_ERROR) {
response_.error = "Failed to receive response";
setReadyState(DONE);
handleError(response_.error);
active_operation_ = false;
return;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Raw HTTP response data (%zu bytes):", response_data.length());
kinc_log(KINC_LOG_LEVEL_INFO, "Response preview: %.200s", response_data.c_str());
parseHttpResponse(response_data);
kinc_log(KINC_LOG_LEVEL_INFO, "Parsed response - status: %d, body length: %zu", response_.status, response_.responseText.length());
kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %.200s", response_.responseText.c_str());
setReadyState(HEADERS_RECEIVED);
setReadyState(DONE);
handleLoad();
handleLoadEnd();
active_operation_ = false;
} catch (const std::exception& e) {
response_.error = e.what();
setReadyState(DONE);
handleError(response_.error);
handleLoadEnd();
active_operation_ = false;
}
}
void HttpRequestClient::setReadyState(ReadyState state) {
if (ready_state_ != state) {
ready_state_ = state;
handleReadyStateChange();
}
}
// event handle is called from the background thread
void HttpRequestClient::handleReadyStateChange() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_READY_STATE_CHANGE));
}
void HttpRequestClient::handleLoad() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_LOAD));
}
void HttpRequestClient::handleError(const std::string& error) {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_ERROR, error));
}
void HttpRequestClient::handleTimeout() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_TIMEOUT));
}
void HttpRequestClient::handleAbort() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_ABORT));
}
void HttpRequestClient::handleProgress() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_PROGRESS));
}
void HttpRequestClient::handleLoadStart() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_LOAD_START));
}
void HttpRequestClient::handleLoadEnd() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
event_queue_.push(Event(HTTP_LOAD_END));
}
// event called from main thread
void HttpRequestClient::processReadyStateChangeEvent() {
if (ready_state_ == DONE && done_event_fired_) {
kinc_log(KINC_LOG_LEVEL_INFO, "Skipping duplicate DONE ready state change event");
return;
}
if (ready_state_ == DONE) {
done_event_fired_ = true;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Processing HTTP ready state change event, ready state: %d", ready_state_);
if (!on_ready_state_change_.IsEmpty()) {
callCallback(on_ready_state_change_, 0, nullptr);
} else {
kinc_log(KINC_LOG_LEVEL_INFO, "No ready state change callback set for HTTP request");
}
}
void HttpRequestClient::processLoadEvent() {
kinc_log(KINC_LOG_LEVEL_INFO, "Processing HTTP load event");
if (!on_load_.IsEmpty()) {
callCallback(on_load_, 0, nullptr);
} else {
kinc_log(KINC_LOG_LEVEL_INFO, "No load callback set for HTTP request");
}
}
void HttpRequestClient::processErrorEvent(const std::string& error) {
kinc_log(KINC_LOG_LEVEL_INFO, "Processing HTTP error event: %s", error.c_str());
if (!on_error_.IsEmpty()) {
Locker locker{isolate_};
Isolate::Scope isolate_scope(isolate_);
HandleScope handle_scope(isolate_);
Local<Context> context = global_context_->Get(isolate_);
Context::Scope context_scope(context);
Local<Object> event = Object::New(isolate_);
event->Set(context, String::NewFromUtf8(isolate_, "message").ToLocalChecked(),
String::NewFromUtf8(isolate_, error.c_str()).ToLocalChecked());
Local<Value> argv[1] = { event };
callCallback(on_error_, 1, argv);
} else {
kinc_log(KINC_LOG_LEVEL_INFO, "No error callback set for HTTP request");
}
}
void HttpRequestClient::processTimeoutEvent() {
if (!on_timeout_.IsEmpty()) {
callCallback(on_timeout_, 0, nullptr);
}
}
void HttpRequestClient::processAbortEvent() {
if (!on_abort_.IsEmpty()) {
callCallback(on_abort_, 0, nullptr);
}
}
void HttpRequestClient::processProgressEvent() {
if (!on_progress_.IsEmpty()) {
callCallback(on_progress_, 0, nullptr);
}
}
void HttpRequestClient::processLoadStartEvent() {
if (!on_load_start_.IsEmpty()) {
callCallback(on_load_start_, 0, nullptr);
}
}
void HttpRequestClient::processLoadEndEvent() {
if (!on_load_end_.IsEmpty()) {
callCallback(on_load_end_, 0, nullptr);
}
}
void HttpRequestClient::processEvents() {
std::lock_guard<std::mutex> lock(event_queue_mutex_);
while (!event_queue_.empty()) {
Event event = event_queue_.front();
event_queue_.pop();
switch (event.type) {
case HTTP_READY_STATE_CHANGE:
processReadyStateChangeEvent();
break;
case HTTP_LOAD:
processLoadEvent();
break;
case HTTP_ERROR:
processErrorEvent(event.data);
break;
case HTTP_TIMEOUT:
processTimeoutEvent();
break;
case HTTP_ABORT:
processAbortEvent();
break;
case HTTP_PROGRESS:
processProgressEvent();
break;
case HTTP_LOAD_START:
processLoadStartEvent();
break;
case HTTP_LOAD_END:
processLoadEndEvent();
break;
}
}
}
void HttpRequestClient::callCallback(Global<Function>& callback, int argc, Local<Value> argv[]) {
if (callback.IsEmpty()) return;
Locker locker{isolate_};
Isolate::Scope isolate_scope(isolate_);
HandleScope handle_scope(isolate_);
Local<Context> context = global_context_->Get(isolate_);
Context::Scope context_scope(context);
TryCatch try_catch(isolate_);
Local<Function> func = callback.Get(isolate_);
Local<Value> result;
if (func->Call(context, context->Global(), argc, argv).ToLocal(&result)) {
// Success
} else {
if (try_catch.HasCaught()) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Error in HTTP request callback");
Local<Value> exception = try_catch.Exception();
String::Utf8Value exception_str(isolate_, exception);
kinc_log(KINC_LOG_LEVEL_ERROR, "Exception details: %s", *exception_str);
}
}
}
bool HttpRequestClient::parseUrl(const std::string& url, std::string& host, int& port, std::string& path, bool& is_https) {
if (url.find("://") == std::string::npos) {
kinc_log(KINC_LOG_LEVEL_INFO, "Detected local file path: %s", url.c_str());
host = "localhost";
port = 0;
path = url;
is_https = false;
return true;
}
if (url.substr(0, 7) == "file://") {
kinc_log(KINC_LOG_LEVEL_INFO, "Detected file:// URL: %s", url.c_str());
host = "localhost";
port = 0; // Special port to indicate local file
path = url.substr(7); // Remove "file://" prefix
is_https = false;
return true;
}
std::regex url_regex(R"(^(https?):\/\/([^\/\s:]+)(?::(\d+))?(\/[^\s]*)?(?:\?[^\s]*)?(?:#[^\s]*)?$)");
std::smatch matches;
kinc_log(KINC_LOG_LEVEL_INFO, "Parsing HTTP URL: %s", url.c_str());
if (!std::regex_match(url, matches, url_regex)) {
// fallback parsing for simpler URLs
std::regex simple_regex(R"(^(https?):\/\/([^\/\s:]+)(?::(\d+))?(?:\/.*)?$)");
if (!std::regex_match(url, matches, simple_regex)) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to parse URL with both regex patterns: %s", url.c_str());
return false;
}
}
std::string scheme = matches[1].str();
host = matches[2].str();
std::string port_str = matches[3].str();
path = matches[4].str();
if (path.empty()) {
path = "/";
}
// remove query string and fragment for HTTP request path
size_t query_pos = path.find('?');
if (query_pos != std::string::npos) {
path = path.substr(0, query_pos);
}
size_t fragment_pos = path.find('#');
if (fragment_pos != std::string::npos) {
path = path.substr(0, fragment_pos);
}
if (path.empty()) {
path = "/";
}
is_https = (scheme == "https");
if (!port_str.empty()) {
try {
port = std::stoi(port_str);
} catch (const std::exception&) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid port number: %s", port_str.c_str());
port = is_https ? 443 : 80;
}
} else {
port = is_https ? 443 : 80;
}
kinc_log(KINC_LOG_LEVEL_INFO, "URL parsed successfully - scheme: %s, host: %s, port: %d, path: %s",
scheme.c_str(), host.c_str(), port, path.c_str());
return true;
}
std::string HttpRequestClient::formatHttpRequest(const std::string& method, const std::string& path, const std::string& host, const std::string& data) {
std::ostringstream request;
request << method << " " << path << " HTTP/1.1\r\n";
request << "Host: " << host << "\r\n";
request << "Connection: close\r\n";
request << "User-Agent: LNXCORE/1.0\r\n";
request << "Accept: */*\r\n";
request << "Accept-Encoding: identity\r\n";
for (const auto& header : request_headers_) {
request << header.first << ": " << header.second << "\r\n";
}
if (!data.empty()) {
request << "Content-Length: " << data.length() << "\r\n";
if (request_headers_.find("Content-Type") == request_headers_.end()) {
request << "Content-Type: application/x-www-form-urlencoded\r\n";
}
}
request << "\r\n";
if (!data.empty()) {
request << data;
}
std::string request_str = request.str();
kinc_log(KINC_LOG_LEVEL_INFO, "Formatted HTTP request (%zu bytes):", request_str.length());
kinc_log(KINC_LOG_LEVEL_INFO, "Request headers: %s", request_str.substr(0, request_str.find("\r\n\r\n")).c_str());
return request_str;
}
void HttpRequestClient::parseHttpResponse(const std::string& response_data) {
std::istringstream stream(response_data);
std::string line;
// initialized with defaults
response_.status = 0;
response_.statusText = "Unknown";
response_.responseText = "";
response_.headers.clear();
if (response_data.empty()) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "HTTP DEBUG: Response data is empty - potential network/SSL failure");
#endif
response_.status = 0;
response_.statusText = "Empty Response";
return;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP DEBUG: Parsing response (%zu bytes), first 200 chars: %.200s", response_data.length(), response_data.c_str());
#endif
if (std::getline(stream, line)) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Status line: %s", line.c_str());
#endif
std::istringstream status_stream(line);
std::string http_version;
std::string status_text_rest;
if (status_stream >> http_version >> response_.status) {
// Read the rest of the line as status text
std::getline(status_stream, status_text_rest);
if (!status_text_rest.empty()) {
// Remove leading space
response_.statusText = status_text_rest.substr(1);
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Parsed status: %d, text: %s", response_.status, response_.statusText.c_str());
#endif
} else {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to parse status line: %s", line.c_str());
#endif
response_.status = 0;
response_.statusText = "Invalid Status Line";
}
} else {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "No status line found in HTTP response");
#endif
response_.status = 0;
response_.statusText = "No Status Line";
}
// Parse headers
int header_count = 0;
while (std::getline(stream, line) && !line.empty() && line != "\r") {
size_t colon_pos = line.find(':');
if (colon_pos != std::string::npos) {
std::string header_name = line.substr(0, colon_pos);
std::string header_value = line.substr(colon_pos + 1);
// Trim whitespace
header_value.erase(0, header_value.find_first_not_of(" \t"));
header_value.erase(header_value.find_last_not_of(" \t\r\n") + 1);
response_.headers[header_name] = header_value;
header_count++;
kinc_log(KINC_LOG_LEVEL_INFO, "Header: %s = %s", header_name.c_str(), header_value.c_str());
}
}
kinc_log(KINC_LOG_LEVEL_INFO, "Parsed %d headers", header_count);
std::ostringstream body_stream;
std::string body_line;
size_t body_lines = 0;
while (std::getline(stream, body_line)) {
if (body_lines > 0) {
body_stream << "\n";
}
body_stream << body_line;
body_lines++;
}
response_.responseText = body_stream.str();
kinc_log(KINC_LOG_LEVEL_INFO, "Parsed body: %zu lines, %zu bytes", body_lines, response_.responseText.length());
if (!response_.responseText.empty()) {
std::string preview = response_.responseText.substr(0, 100);
kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %s", preview.c_str());
}
}
void HttpRequestClient::loadLocalFile(const std::string& path) {
std::ifstream file(path, std::ios::in | std::ios::binary);
if (!file.is_open()) {
response_.error = "Failed to open local file: " + path;
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to open local file: %s", path.c_str());
setReadyState(DONE);
handleError(response_.error);
return;
}
file.seekg(0, std::ios::end);
size_t file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::string file_content(file_size, '\0');
file.read(&file_content[0], file_size);
file.close();
if (file.fail() && !file.eof()) {
response_.error = "Failed to read local file: " + path;
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to read local file: %s", path.c_str());
setReadyState(DONE);
handleError(response_.error);
return;
}
response_.responseText = file_content;
response_.status = 200;
response_.statusText = "OK";
std::string content_type = "application/octet-stream";
size_t dot_pos = path.find_last_of('.');
if (dot_pos != std::string::npos) {
std::string extension = path.substr(dot_pos);
if (extension == ".js") {
content_type = "application/javascript";
} else if (extension == ".html") {
content_type = "text/html";
} else if (extension == ".css") {
content_type = "text/css";
} else if (extension == ".json") {
content_type = "application/json";
} else if (extension == ".txt") {
content_type = "text/plain";
}
}
response_.headers["Content-Type"] = content_type;
response_.headers["Content-Length"] = std::to_string(file_size);
kinc_log(KINC_LOG_LEVEL_INFO, "Local file loaded: %s, size: %zu, content-type: %s",
path.c_str(), file_size, content_type.c_str());
setReadyState(DONE);
handleLoad();
}
#ifdef WITH_SSL
bool HttpRequestClient::initSSL() {
#ifdef _WIN32
// windows SChannel implementation
if (ssl_context_initialized_) {
return true;
}
ssl_cred_handle_ = new CredHandle();
memset(ssl_cred_handle_, 0, sizeof(CredHandle));
SCHANNEL_CRED cred = {0};
cred.dwVersion = SCHANNEL_CRED_VERSION;
cred.grbitEnabledProtocols = SP_PROT_TLS1_2 | SP_PROT_TLS1_1;
cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
cred.dwMinimumCipherStrength = 0;
cred.dwMaximumCipherStrength = 0;
SECURITY_STATUS status = AcquireCredentialsHandleA(
NULL, const_cast<LPSTR>(UNISP_NAME_A), SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, NULL,
reinterpret_cast<PCredHandle>(ssl_cred_handle_), NULL);
if (status != SEC_E_OK) {
const char* cred_error_desc = "Unknown credential error";
switch (status) {
case SEC_E_SECPKG_NOT_FOUND: cred_error_desc = "SEC_E_SECPKG_NOT_FOUND - Security package not found"; break;
case SEC_E_NOT_OWNER: cred_error_desc = "SEC_E_NOT_OWNER - Not owner"; break;
case SEC_E_CANNOT_INSTALL: cred_error_desc = "SEC_E_CANNOT_INSTALL - Cannot install"; break;
case SEC_E_INVALID_TOKEN: cred_error_desc = "SEC_E_INVALID_TOKEN - Invalid token"; break;
case SEC_E_LOGON_DENIED: cred_error_desc = "SEC_E_LOGON_DENIED - Logon denied"; break;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to acquire SSL credentials: 0x%x (%s)", status, cred_error_desc);
if (ssl_cred_handle_) {
delete reinterpret_cast<CredHandle*>(ssl_cred_handle_);
ssl_cred_handle_ = nullptr;
}
return false;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Successfully acquired SSL credentials");
ssl_context_initialized_ = true;
return true;
#else
// OpenSSL implementation
if (ssl_initialized_) {
return true;
}
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
ssl_ctx_ = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx_) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create SSL context");
return false;
}
SSL_CTX_set_options(ssl_ctx_, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_default_verify_paths(ssl_ctx_);
// SSL OPTIMIZATIONS - Session resumption
SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT);
SSL_CTX_set_timeout(ssl_ctx_, 300);
// optimized cipher list
SSL_CTX_set_cipher_list(ssl_ctx_, "ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM");
ssl_initialized_ = true;
return true;
#endif
}
void HttpRequestClient::cleanupSSL() {
#ifdef _WIN32
if (ssl_context_initialized_) {
if (ssl_context_handle_) {
DeleteSecurityContext(reinterpret_cast<PCtxtHandle>(ssl_context_handle_));
delete reinterpret_cast<CtxtHandle*>(ssl_context_handle_);
ssl_context_handle_ = nullptr;
}
if (ssl_cred_handle_) {
FreeCredentialsHandle(reinterpret_cast<PCredHandle>(ssl_cred_handle_));
delete reinterpret_cast<CredHandle*>(ssl_cred_handle_);
ssl_cred_handle_ = nullptr;
}
ssl_context_initialized_ = false;
}
ssl_buffer_.clear();
ssl_socket_ = -1;
#else
if (ssl_) {
SSL_free(ssl_);
ssl_ = nullptr;
}
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
ssl_ctx_ = nullptr;
}
ssl_initialized_ = false;
#endif
}
bool HttpRequestClient::performSSLHandshake(int socket, const std::string& host) {
if (!initSSL()) {
return false;
}
#ifdef _WIN32
// windows SChannel handshake
#if DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "HTTP DEBUG: Performing SChannel SSL handshake for host: %s", host.c_str());
#endif
kinc_log(KINC_LOG_LEVEL_INFO, "Performing SChannel SSL handshake...");
const int MAX_SSL_RETRIES = 3;
int retry_count = 0;
SecBufferDesc outbuffer_desc, inbuffer_desc;
SecBuffer outbuffers[1], inbuffers[2];
DWORD context_attributes;
TimeStamp expiry;
outbuffers[0].pvBuffer = nullptr;
outbuffers[0].BufferType = SECBUFFER_TOKEN;
outbuffers[0].cbBuffer = 0;
outbuffer_desc.cBuffers = 1;
outbuffer_desc.pBuffers = outbuffers;
outbuffer_desc.ulVersion = SECBUFFER_VERSION;
inbuffers[0].pvBuffer = nullptr;
inbuffers[0].BufferType = SECBUFFER_TOKEN;
inbuffers[0].cbBuffer = 0;
inbuffers[1].pvBuffer = nullptr;
inbuffers[1].BufferType = SECBUFFER_EMPTY;
inbuffers[1].cbBuffer = 0;
inbuffer_desc.cBuffers = 2;
inbuffer_desc.pBuffers = inbuffers;
inbuffer_desc.ulVersion = SECBUFFER_VERSION;
SECURITY_STATUS status = SEC_I_CONTINUE_NEEDED;
bool first_call = true;
std::vector<char> handshake_buffer;
while (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_INCOMPLETE_MESSAGE) {
if (first_call) {
// first call should use NULL for phContext
ssl_context_handle_ = new CtxtHandle();
memset(ssl_context_handle_, 0, sizeof(CtxtHandle));
kinc_log(KINC_LOG_LEVEL_INFO, "Calling InitializeSecurityContextA for first handshake...");
kinc_log(KINC_LOG_LEVEL_INFO, "Buffer before call: cbBuffer=%d, pvBuffer=%p", outbuffers[0].cbBuffer, outbuffers[0].pvBuffer);
status = InitializeSecurityContextA(
reinterpret_cast<PCredHandle>(ssl_cred_handle_),
nullptr, // phContext = NULL for first call
const_cast<char*>(host.c_str()),
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY | ISC_REQ_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM,
0, SECURITY_NATIVE_DREP, nullptr, 0,
reinterpret_cast<PCtxtHandle>(ssl_context_handle_), // phNewContext receives the new context
&outbuffer_desc, &context_attributes, &expiry);
kinc_log(KINC_LOG_LEVEL_INFO, "InitializeSecurityContextA result: status=0x%x", status);
kinc_log(KINC_LOG_LEVEL_INFO, "Buffer after call: cbBuffer=%d, pvBuffer=%p", outbuffers[0].cbBuffer, outbuffers[0].pvBuffer);
if (status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) {
const char* init_error_desc = "Unknown initialization error";
switch (status) {
case SEC_E_INVALID_HANDLE: init_error_desc = "SEC_E_INVALID_HANDLE - Invalid handle"; break;
case SEC_E_INVALID_TOKEN: init_error_desc = "SEC_E_INVALID_TOKEN - Invalid token"; break;
case SEC_E_LOGON_DENIED: init_error_desc = "SEC_E_LOGON_DENIED - Logon denied"; break;
case SEC_E_TARGET_UNKNOWN: init_error_desc = "SEC_E_TARGET_UNKNOWN - Target unknown"; break;
case SEC_E_INTERNAL_ERROR: init_error_desc = "SEC_E_INTERNAL_ERROR - Internal error"; break;
case SEC_E_SECPKG_NOT_FOUND: init_error_desc = "SEC_E_SECPKG_NOT_FOUND - Security package not found"; break;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "InitializeSecurityContextA failed: 0x%x (%s)", status, init_error_desc);
if (ssl_context_handle_) {
delete reinterpret_cast<CtxtHandle*>(ssl_context_handle_);
ssl_context_handle_ = nullptr;
}
return false;
}
first_call = false;
} else {
outbuffers[0].pvBuffer = nullptr;
outbuffers[0].BufferType = SECBUFFER_TOKEN;
outbuffers[0].cbBuffer = 0;
if (!handshake_buffer.empty()) {
inbuffers[0].pvBuffer = handshake_buffer.data();
inbuffers[0].cbBuffer = static_cast<unsigned long>(handshake_buffer.size());
inbuffers[0].BufferType = SECBUFFER_TOKEN;
inbuffers[1].pvBuffer = nullptr;
inbuffers[1].BufferType = SECBUFFER_EMPTY;
inbuffers[1].cbBuffer = 0;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Calling InitializeSecurityContextA for continuation handshake...");
kinc_log(KINC_LOG_LEVEL_INFO, "Input buffer: cbBuffer=%d, pvBuffer=%p", inbuffers[0].cbBuffer, inbuffers[0].pvBuffer);
kinc_log(KINC_LOG_LEVEL_INFO, "Context handle: %p", ssl_context_handle_);
status = InitializeSecurityContextA(
reinterpret_cast<PCredHandle>(ssl_cred_handle_),
reinterpret_cast<PCtxtHandle>(ssl_context_handle_),
const_cast<char*>(host.c_str()),
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY | ISC_REQ_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM,
0, SECURITY_NATIVE_DREP, &inbuffer_desc, 0,
reinterpret_cast<PCtxtHandle>(ssl_context_handle_),
&outbuffer_desc, &context_attributes, &expiry);
kinc_log(KINC_LOG_LEVEL_INFO, "Continuation InitializeSecurityContextA result: status=0x%x", status);
kinc_log(KINC_LOG_LEVEL_INFO, "Output buffer after continuation: cbBuffer=%d, pvBuffer=%p", outbuffers[0].cbBuffer, outbuffers[0].pvBuffer);
// handle where some data was consumed and check if extra data was left in the buffer
if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) {
if (inbuffers[1].BufferType == SECBUFFER_EXTRA && inbuffers[1].cbBuffer > 0) {
// move extra data to the beginning of the buffer
size_t extra_size = static_cast<size_t>(inbuffers[1].cbBuffer);
size_t consumed = handshake_buffer.size() - extra_size;
std::memmove(handshake_buffer.data(), handshake_buffer.data() + consumed, extra_size);
handshake_buffer.resize(extra_size);
} else {
// all consumed
handshake_buffer.clear();
}
}
}
if (status == SEC_I_CONTINUE_NEEDED && outbuffers[0].cbBuffer > 0 && outbuffers[0].pvBuffer != nullptr) {
if (outbuffers[0].cbBuffer < 0 || outbuffers[0].cbBuffer > 65536) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid SSL handshake buffer size: %d bytes (corrupted data)", outbuffers[0].cbBuffer);
if (outbuffers[0].pvBuffer) FreeContextBuffer(outbuffers[0].pvBuffer);
return false;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Sending SSL handshake data: %d bytes", outbuffers[0].cbBuffer);
int sent = ::send(socket, static_cast<char*>(outbuffers[0].pvBuffer),
outbuffers[0].cbBuffer, 0);
if (sent <= 0) {
int error = WSAGetLastError();
const char* error_desc = "Unknown socket error";
switch (error) {
case WSAENOTCONN: error_desc = "WSAENOTCONN - Socket is not connected"; break;
case WSAECONNRESET: error_desc = "WSAECONNRESET - Connection reset by peer"; break;
case WSAEWOULDBLOCK: error_desc = "WSAEWOULDBLOCK - Resource temporarily unavailable"; break;
case WSAENETDOWN: error_desc = "WSAENETDOWN - Network is down"; break;
case WSAEFAULT: error_desc = "WSAEFAULT - Bad address"; break;
case WSAEINVAL: error_desc = "WSAEINVAL - Invalid argument"; break;
case WSAENOTSOCK: error_desc = "WSAENOTSOCK - Socket operation on non-socket"; break;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send SSL handshake data: sent=%d, error=%d (%s)", sent, error, error_desc);
if (outbuffers[0].pvBuffer) FreeContextBuffer(outbuffers[0].pvBuffer);
return false;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Successfully sent SSL handshake data: %d bytes", sent);
FreeContextBuffer(outbuffers[0].pvBuffer);
outbuffers[0].pvBuffer = nullptr;
outbuffers[0].cbBuffer = 0;
}
if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_INCOMPLETE_MESSAGE) {
char recv_buffer[8192];
kinc_log(KINC_LOG_LEVEL_INFO, "Waiting to receive SSL handshake response...");
int received = ::recv(socket, recv_buffer, sizeof(recv_buffer), 0);
if (received <= 0) {
int error = WSAGetLastError();
const char* error_desc = "Unknown socket error";
switch (error) {
case WSAENOTCONN: error_desc = "WSAENOTCONN - Socket is not connected"; break;
case WSAECONNRESET: error_desc = "WSAECONNRESET - Connection reset by peer"; break;
case WSAEWOULDBLOCK: error_desc = "WSAEWOULDBLOCK - Resource temporarily unavailable"; break;
case WSAENETDOWN: error_desc = "WSAENETDOWN - Network is down"; break;
case WSAEFAULT: error_desc = "WSAEFAULT - Bad address"; break;
case WSAEINVAL: error_desc = "WSAEINVAL - Invalid argument"; break;
case WSAENOTSOCK: error_desc = "WSAENOTSOCK - Socket operation on non-socket"; break;
case WSAECONNABORTED: error_desc = "WSAECONNABORTED - Connection aborted"; break;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive SSL handshake data: received=%d, error=%d (%s)", received, error, error_desc);
return false;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Received SSL handshake response: %d bytes", received);
handshake_buffer.insert(handshake_buffer.end(), recv_buffer, recv_buffer + received);
}
}
if (status == SEC_E_OK) {
ssl_context_initialized_ = true;
kinc_log(KINC_LOG_LEVEL_INFO, "SChannel SSL handshake completed successfully");
kinc_log(KINC_LOG_LEVEL_INFO, "SSL buffer size after handshake: %zu bytes", ssl_buffer_.size());
return true;
} else {
const char* error_desc = "Unknown error";
switch (status) {
case SEC_E_LOGON_DENIED: error_desc = "SEC_E_LOGON_DENIED - The logon attempt failed"; break;
case SEC_E_INVALID_TOKEN: error_desc = "SEC_E_INVALID_TOKEN - Invalid security token"; break;
case SEC_E_INVALID_HANDLE: error_desc = "SEC_E_INVALID_HANDLE - Invalid handle"; break;
case SEC_E_INTERNAL_ERROR: error_desc = "SEC_E_INTERNAL_ERROR - Internal error"; break;
case SEC_E_NO_CREDENTIALS: error_desc = "SEC_E_NO_CREDENTIALS - No credentials available"; break;
case SEC_E_WRONG_PRINCIPAL: error_desc = "SEC_E_WRONG_PRINCIPAL - Wrong principal"; break;
case SEC_E_CERT_EXPIRED: error_desc = "SEC_E_CERT_EXPIRED - Certificate expired"; break;
case SEC_E_CERT_UNKNOWN: error_desc = "SEC_E_CERT_UNKNOWN - Certificate unknown"; break;
case SEC_E_UNTRUSTED_ROOT: error_desc = "SEC_E_UNTRUSTED_ROOT - Untrusted root certificate"; break;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel SSL handshake failed with status: 0x%x (%s)", status, error_desc);
return false;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL handshake failed after %d retries", MAX_SSL_RETRIES);
return false;
#else
ssl_ = SSL_new(ssl_ctx_);
if (!ssl_) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create SSL structure");
return false;
}
if (SSL_set_fd(ssl_, socket) != 1) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set SSL socket");
SSL_free(ssl_);
ssl_ = nullptr;
return false;
}
SSL_set_tlsext_host_name(ssl_, host.c_str());
int result = SSL_connect(ssl_);
if (result != 1) {
int error = SSL_get_error(ssl_, result);
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL handshake failed: %d", error);
SSL_free(ssl_);
ssl_ = nullptr;
return false;
}
ssl_initialized_ = true;
kinc_log(KINC_LOG_LEVEL_INFO, "OpenSSL handshake completed successfully");
return true;
#endif
}
int HttpRequestClient::sslRead(char* buffer, int length) {
#ifdef _WIN32
// windows SChannel read implementation with SEC_E_INCOMPLETE_MESSAGE handling
if (!ssl_context_initialized_) {
return -1;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Enhanced sslRead called, SSL buffer size: %zu bytes", ssl_buffer_.size());
const int MAX_SSL_RETRIES = 3;
const int SSL_TIMEOUT_MS = 30000;
int retry_count = 0;
SECURITY_STATUS status = SEC_E_OK;
while (retry_count < MAX_SSL_RETRIES) {
if (ssl_buffer_.empty()) {
kinc_log(KINC_LOG_LEVEL_INFO, "SSL buffer empty, reading encrypted data from socket...");
char temp_buffer[8192];
// use shorter timeout and non-blocking approach
int received = receiveWithTimeout(ssl_socket_, temp_buffer, sizeof(temp_buffer), 5000); // 5 sec timeout
kinc_log(KINC_LOG_LEVEL_INFO, "receiveWithTimeout returned: %d bytes", received);
if (received > 0) {
ssl_buffer_.insert(ssl_buffer_.end(), temp_buffer, temp_buffer + received);
kinc_log(KINC_LOG_LEVEL_INFO, "Read %d encrypted bytes from socket, SSL buffer now: %zu bytes",
received, ssl_buffer_.size());
} else if (received == 0) {
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server (no data available)");
return 0;
} else {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to read SSL data from socket (timeout or error)");
return -1;
}
}
if (!ssl_buffer_.empty()) {
kinc_log(KINC_LOG_LEVEL_INFO, "Processing SSL buffer: %zu bytes, attempt %d",
ssl_buffer_.size(), retry_count + 1);
SecBufferDesc message_desc;
SecBuffer message_buffers[4];
message_desc.ulVersion = SECBUFFER_VERSION;
message_desc.cBuffers = 4;
message_desc.pBuffers = message_buffers;
message_buffers[0].BufferType = SECBUFFER_DATA;
message_buffers[0].pvBuffer = ssl_buffer_.data();
message_buffers[0].cbBuffer = static_cast<unsigned long>(ssl_buffer_.size());
message_buffers[1].BufferType = SECBUFFER_EMPTY;
message_buffers[1].pvBuffer = nullptr;
message_buffers[1].cbBuffer = 0;
message_buffers[2].BufferType = SECBUFFER_EMPTY;
message_buffers[2].pvBuffer = nullptr;
message_buffers[2].cbBuffer = 0;
message_buffers[3].BufferType = SECBUFFER_EMPTY;
message_buffers[3].pvBuffer = nullptr;
message_buffers[3].cbBuffer = 0;
status = DecryptMessage(
reinterpret_cast<PCtxtHandle>(ssl_context_handle_),
&message_desc, 0, nullptr);
if (status == SEC_E_OK) {
SecBuffer* data_buffer = nullptr;
SecBuffer* extra_buffer = nullptr;
for (int i = 0; i < 4; i++) {
if (message_buffers[i].BufferType == SECBUFFER_DATA) {
data_buffer = &message_buffers[i];
} else if (message_buffers[i].BufferType == SECBUFFER_EXTRA) {
extra_buffer = &message_buffers[i];
}
}
if (data_buffer && data_buffer->cbBuffer > 0) {
int bytes_to_copy = (length < (int)data_buffer->cbBuffer) ? length : (int)data_buffer->cbBuffer;
memcpy(buffer, data_buffer->pvBuffer, static_cast<size_t>(bytes_to_copy));
if (extra_buffer && extra_buffer->cbBuffer > 0) {
std::memmove(ssl_buffer_.data(), extra_buffer->pvBuffer, static_cast<size_t>(extra_buffer->cbBuffer));
ssl_buffer_.resize(static_cast<size_t>(extra_buffer->cbBuffer));
} else {
ssl_buffer_.clear();
}
return bytes_to_copy;
}
ssl_buffer_.clear();
} else if (status == SEC_E_INCOMPLETE_MESSAGE) {
// expected behavior
kinc_log(KINC_LOG_LEVEL_INFO, "SSL decrypt needs more data (SEC_E_INCOMPLETE_MESSAGE) - reading more...");
char recv_buffer[8192];
int received = receiveWithTimeout(ssl_socket_, recv_buffer, sizeof(recv_buffer), SSL_TIMEOUT_MS);
if (received > 0) {
ssl_buffer_.insert(ssl_buffer_.end(), recv_buffer, recv_buffer + received);
kinc_log(KINC_LOG_LEVEL_INFO, "Received %d more SSL bytes, total buffer: %zu",
received, ssl_buffer_.size());
continue;
} else if (received == 0) {
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed gracefully by server");
return 0;
} else {
// leftover data treated as a connection close
if (ssl_buffer_.size() < 50) {
kinc_log(KINC_LOG_LEVEL_INFO, "Incomplete message with small buffer (%zu bytes) and no more data - likely connection close",
ssl_buffer_.size());
ssl_buffer_.clear();
return 0;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive additional SSL data (timeout or error)");
return -1;
}
} else if (status == SEC_I_CONTEXT_EXPIRED || status == 0x00090317) {
kinc_log(KINC_LOG_LEVEL_INFO, "SSL context expired in inner loop");
ssl_buffer_.clear();
return 0;
} else {
// small buffer is likely connection close with partial data
if (ssl_buffer_.size() < 50) {
kinc_log(KINC_LOG_LEVEL_INFO, "SChannel status 0x%x with small buffer (%zu bytes) - treating as connection close",
status, ssl_buffer_.size());
ssl_buffer_.clear();
return 0;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel decrypt failed: 0x%x", status);
return -1;
}
ssl_buffer_.clear();
} else if (status == SEC_E_INCOMPLETE_MESSAGE) {
// expected behavior
kinc_log(KINC_LOG_LEVEL_INFO, "SSL decrypt needs more data (SEC_E_INCOMPLETE_MESSAGE) - reading more...");
char recv_buffer[8192];
kinc_log(KINC_LOG_LEVEL_INFO, "About to call receiveWithTimeout on socket, timeout=%d ms", SSL_TIMEOUT_MS);
int received = receiveWithTimeout(ssl_socket_, recv_buffer, sizeof(recv_buffer), SSL_TIMEOUT_MS);
kinc_log(KINC_LOG_LEVEL_INFO, "receiveWithTimeout returned: %d bytes", received);
if (received > 0) {
ssl_buffer_.insert(ssl_buffer_.end(), recv_buffer, recv_buffer + received);
kinc_log(KINC_LOG_LEVEL_INFO, "Received %d more SSL bytes, total buffer: %zu",
received, ssl_buffer_.size());
continue;
} else if (received == 0) {
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed gracefully by server");
return 0;
} else {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive additional SSL data (timeout or error)");
return -1;
}
} else if (status == SEC_I_CONTEXT_EXPIRED || status == 0x00090317) {
// closed by server - SEC_I_CONTEXT_EXPIRED = 0x00090317 is normal for HTTP with "Connection: close"
kinc_log(KINC_LOG_LEVEL_INFO, "SSL context expired (connection closed by server)");
ssl_buffer_.clear();
return 0;
} else if (status == SEC_I_RENEGOTIATE) {
kinc_log(KINC_LOG_LEVEL_INFO, "SSL renegotiation requested, treating as connection close");
ssl_buffer_.clear();
return 0;
} else {
// possible when connection closes with partial SSL record
if (ssl_buffer_.size() < 50) {
kinc_log(KINC_LOG_LEVEL_INFO, "SChannel decrypt status 0x%x with small buffer (%zu bytes) - likely connection close",
status, ssl_buffer_.size());
ssl_buffer_.clear();
return 0;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel decrypt failed: 0x%x", status);
return -1;
}
retry_count++;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL read failed after %d retries", MAX_SSL_RETRIES);
return -1;
#else
if (!ssl_) {
return -1;
}
int bytes_read = SSL_read(ssl_, buffer, length);
if (bytes_read <= 0) {
int error = SSL_get_error(ssl_, bytes_read);
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
return 0;
}
return -1;
}
return bytes_read;
#endif
}
int HttpRequestClient::sslWrite(const char* buffer, int length) {
#ifdef _WIN32
// windows SChannel write implementation
if (!ssl_context_initialized_) {
return -1;
}
SecBufferDesc message_desc;
SecBuffer message_buffers[4];
SecPkgContext_StreamSizes stream_sizes;
SECURITY_STATUS status = QueryContextAttributesA(
reinterpret_cast<PCtxtHandle>(ssl_context_handle_),
SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
if (status != SEC_E_OK) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to get stream sizes: 0x%x", status);
return -1;
}
int total_size = stream_sizes.cbHeader + length + stream_sizes.cbTrailer;
char* encrypt_buffer = new char[total_size];
message_desc.ulVersion = SECBUFFER_VERSION;
message_desc.cBuffers = 4;
message_desc.pBuffers = message_buffers;
message_buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
message_buffers[0].pvBuffer = encrypt_buffer;
message_buffers[0].cbBuffer = stream_sizes.cbHeader;
message_buffers[1].BufferType = SECBUFFER_DATA;
message_buffers[1].pvBuffer = encrypt_buffer + stream_sizes.cbHeader;
message_buffers[1].cbBuffer = length;
memcpy(message_buffers[1].pvBuffer, buffer, length);
message_buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
message_buffers[2].pvBuffer = encrypt_buffer + stream_sizes.cbHeader + length;
message_buffers[2].cbBuffer = stream_sizes.cbTrailer;
message_buffers[3].BufferType = SECBUFFER_EMPTY;
message_buffers[3].pvBuffer = nullptr;
message_buffers[3].cbBuffer = 0;
status = EncryptMessage(
reinterpret_cast<PCtxtHandle>(ssl_context_handle_),
0, &message_desc, 0);
if (status != SEC_E_OK) {
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel encrypt failed: 0x%x", status);
delete[] encrypt_buffer;
return -1;
}
int total_bytes_to_send = stream_sizes.cbHeader + length + stream_sizes.cbTrailer;
int bytes_sent = ::send(ssl_socket_, encrypt_buffer, total_bytes_to_send, 0);
kinc_log(KINC_LOG_LEVEL_INFO, "SSL encrypted and sent %d bytes (original: %d)", bytes_sent, length);
delete[] encrypt_buffer;
if (bytes_sent == total_bytes_to_send) {
// return original length on success
return length;
} else {
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL send failed: sent %d of %d bytes", bytes_sent, total_bytes_to_send);
return -1;
}
#else
if (!ssl_) {
return -1;
}
int bytes_written = SSL_write(ssl_, buffer, length);
if (bytes_written <= 0) {
int error = SSL_get_error(ssl_, bytes_written);
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
return 0;
}
return -1;
}
return bytes_written;
#endif
}
#endif
}
// V8 binding functions
void runt_httprequest_create(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
int id = HttpRequestWrapper::createHttpRequest(isolate);
args.GetReturnValue().Set(Number::New(isolate, id));
}
void runt_httprequest_open(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() || !args[2]->IsString()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
String::Utf8Value method(isolate, args[1]);
String::Utf8Value url(isolate, args[2]);
bool async = args.Length() > 3 ? args[3]->BooleanValue(isolate) : true;
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->open(*method, *url, async);
}
}
void runt_httprequest_send(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
std::string data = "";
if (args.Length() > 1 && args[1]->IsString()) {
String::Utf8Value data_str(isolate, args[1]);
data = *data_str;
}
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->send(data);
}
}
void runt_httprequest_abort(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->abort();
}
}
void runt_httprequest_set_request_header(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() || !args[2]->IsString()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
String::Utf8Value header(isolate, args[1]);
String::Utf8Value value(isolate, args[2]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setRequestHeader(*header, *value);
}
}
void runt_httprequest_get_all_response_headers(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
std::string headers = it->second->getAllResponseHeaders();
args.GetReturnValue().Set(String::NewFromUtf8(isolate, headers.c_str()).ToLocalChecked());
} else {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "").ToLocalChecked());
}
}
void runt_httprequest_get_response_header(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
String::Utf8Value header(isolate, args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
std::string value = it->second->getResponseHeader(*header);
args.GetReturnValue().Set(String::NewFromUtf8(isolate, value.c_str()).ToLocalChecked());
} else {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "").ToLocalChecked());
}
}
void runt_httprequest_get_ready_state(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
args.GetReturnValue().Set(Number::New(isolate, it->second->getReadyState()));
} else {
args.GetReturnValue().Set(Number::New(isolate, 0));
}
}
void runt_httprequest_get_status(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
args.GetReturnValue().Set(Number::New(isolate, it->second->getStatus()));
} else {
args.GetReturnValue().Set(Number::New(isolate, 0));
}
}
void runt_httprequest_get_status_text(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
std::string statusText = it->second->getStatusText();
args.GetReturnValue().Set(String::NewFromUtf8(isolate, statusText.c_str()).ToLocalChecked());
} else {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "").ToLocalChecked());
}
}
void runt_httprequest_get_response_text(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
std::string responseText = it->second->getResponseText();
if (responseText.empty()) {
kinc_log(KINC_LOG_LEVEL_WARNING, "HTTP response text is empty for request ID: %d", id);
}
// ArrayBuffer response type, return an ArrayBuffer instead of string
if (it->second->getResponseType() == HttpRequestWrapper::ARRAY_BUFFER) {
if (responseText.empty()) {
Local<ArrayBuffer> buffer = ArrayBuffer::New(isolate, 0);
args.GetReturnValue().Set(buffer);
} else {
size_t length = responseText.length();
std::unique_ptr<v8::BackingStore> backing = v8::ArrayBuffer::NewBackingStore(isolate, length);
memcpy(backing->Data(), responseText.c_str(), length);
Local<ArrayBuffer> buffer = ArrayBuffer::New(isolate, std::move(backing));
args.GetReturnValue().Set(buffer);
}
} else {
if (responseText.empty()) {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "").ToLocalChecked());
} else {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, responseText.c_str()).ToLocalChecked());
}
}
} else {
kinc_log(KINC_LOG_LEVEL_ERROR, "HTTP request not found for ID: %d", id);
// return empty ArrayBuffer instead of empty string to prevent hxBytes errors
Local<ArrayBuffer> buffer = ArrayBuffer::New(isolate, 0);
args.GetReturnValue().Set(buffer);
}
}
void runt_httprequest_get_response_url(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
std::string responseURL = it->second->getResponseURL();
args.GetReturnValue().Set(String::NewFromUtf8(isolate, responseURL.c_str()).ToLocalChecked());
} else {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "").ToLocalChecked());
}
}
void runt_httprequest_set_timeout(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
int timeout = args[1]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setTimeout(timeout);
}
}
void runt_httprequest_set_response_type(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
int type = args[1]->Int32Value(isolate->GetCurrentContext()).ToChecked();
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setResponseType(static_cast<HttpRequestWrapper::ResponseType>(type));
}
}
void runt_httprequest_set_with_credentials(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsBoolean()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
bool withCredentials = args[1]->BooleanValue(isolate);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setWithCredentials(withCredentials);
}
}
void runt_httprequest_set_onreadystatechange(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnReadyStateChange(callback);
}
}
void runt_httprequest_set_onload(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnLoad(callback);
}
}
void runt_httprequest_set_onerror(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnError(callback);
}
}
void runt_httprequest_set_ontimeout(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnTimeout(callback);
}
}
void runt_httprequest_set_onabort(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnAbort(callback);
}
}
void runt_httprequest_set_onprogress(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnProgress(callback);
}
}
void runt_httprequest_set_onloadstart(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnLoadStart(callback);
}
}
void runt_httprequest_set_onloadend(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked()));
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
Local<Function> callback = Local<Function>::Cast(args[1]);
auto it = HttpRequestWrapper::active_requests.find(id);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setOnLoadEnd(callback);
}
}
// Native XMLHttpRequest class implementation for V8
static void XMLHttpRequestConstructor(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
if (!args.IsConstructCall()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "XMLHttpRequest constructor requires 'new'").ToLocalChecked()));
return;
}
int reqId = HttpRequestWrapper::createHttpRequest(isolate);
Local<Object> instance = args.This();
instance->Set(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked(), Integer::New(isolate, reqId));
instance->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 0));
instance->Set(context, String::NewFromUtf8(isolate, "response").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "responseText").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked());
instance->Set(context, String::NewFromUtf8(isolate, "responseType").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked());
instance->Set(context, String::NewFromUtf8(isolate, "responseURL").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked());
instance->Set(context, String::NewFromUtf8(isolate, "status").ToLocalChecked(), Integer::New(isolate, 0));
instance->Set(context, String::NewFromUtf8(isolate, "statusText").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked());
instance->Set(context, String::NewFromUtf8(isolate, "timeout").ToLocalChecked(), Integer::New(isolate, 0));
instance->Set(context, String::NewFromUtf8(isolate, "withCredentials").ToLocalChecked(), Boolean::New(isolate, false));
instance->Set(context, String::NewFromUtf8(isolate, "onreadystatechange").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "onload").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "onerror").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "onabort").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "ontimeout").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "onprogress").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "onloadstart").ToLocalChecked(), Null(isolate));
instance->Set(context, String::NewFromUtf8(isolate, "onloadend").ToLocalChecked(), Null(isolate));
args.GetReturnValue().Set(instance);
}
static void XMLHttpRequestOpen(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Local<Object> self = args.Holder();
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Method and URL required").ToLocalChecked()));
return;
}
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
int reqId = idVal->Int32Value(context).FromJust();
String::Utf8Value method(isolate, args[0]);
String::Utf8Value url(isolate, args[1]);
bool async = args.Length() > 2 ? args[2]->BooleanValue(isolate) : true;
auto it = HttpRequestWrapper::active_requests.find(reqId);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->open(*method, *url, async);
self->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(),
Integer::New(isolate, it->second->getReadyState()));
}
}
static void XMLHttpRequestSend(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Local<Object> self = args.Holder();
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
int reqId = idVal->Int32Value(context).FromJust();
std::string data = "";
if (args.Length() > 0 && args[0]->IsString()) {
String::Utf8Value str(isolate, args[0]);
data = *str;
}
auto it = HttpRequestWrapper::active_requests.find(reqId);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->send(data);
}
}
static void XMLHttpRequestAbort(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Local<Object> self = args.Holder();
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
int reqId = idVal->Int32Value(context).FromJust();
auto it = HttpRequestWrapper::active_requests.find(reqId);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->abort();
}
}
static void XMLHttpRequestSetRequestHeader(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Local<Object> self = args.Holder();
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Header name and value required").ToLocalChecked()));
return;
}
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
int reqId = idVal->Int32Value(context).FromJust();
String::Utf8Value header(isolate, args[0]);
String::Utf8Value value(isolate, args[1]);
auto it = HttpRequestWrapper::active_requests.find(reqId);
if (it != HttpRequestWrapper::active_requests.end()) {
it->second->setRequestHeader(*header, *value);
}
}
static void XMLHttpRequestGetAllResponseHeaders(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Local<Object> self = args.Holder();
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
int reqId = idVal->Int32Value(context).FromJust();
auto it = HttpRequestWrapper::active_requests.find(reqId);
if (it != HttpRequestWrapper::active_requests.end()) {
std::string headers = it->second->getAllResponseHeaders();
args.GetReturnValue().Set(String::NewFromUtf8(isolate, headers.c_str()).ToLocalChecked());
} else {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "").ToLocalChecked());
}
}
static void XMLHttpRequestGetResponseHeader(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Local<Object> self = args.Holder();
if (args.Length() < 1 || !args[0]->IsString()) {
args.GetReturnValue().SetNull();
return;
}
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
int reqId = idVal->Int32Value(context).FromJust();
String::Utf8Value header(isolate, args[0]);
auto it = HttpRequestWrapper::active_requests.find(reqId);
if (it != HttpRequestWrapper::active_requests.end()) {
std::string value = it->second->getResponseHeader(*header);
if (value.empty()) {
args.GetReturnValue().SetNull();
} else {
args.GetReturnValue().Set(String::NewFromUtf8(isolate, value.c_str()).ToLocalChecked());
}
} else {
args.GetReturnValue().SetNull();
}
}
void createXMLHttpRequestClass(Isolate* isolate, Local<ObjectTemplate>& global) {
Local<FunctionTemplate> xhrTpl = FunctionTemplate::New(isolate, XMLHttpRequestConstructor);
xhrTpl->SetClassName(String::NewFromUtf8(isolate, "XMLHttpRequest").ToLocalChecked());
xhrTpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<ObjectTemplate> proto = xhrTpl->PrototypeTemplate();
proto->Set(isolate, "open", FunctionTemplate::New(isolate, XMLHttpRequestOpen));
proto->Set(isolate, "send", FunctionTemplate::New(isolate, XMLHttpRequestSend));
proto->Set(isolate, "abort", FunctionTemplate::New(isolate, XMLHttpRequestAbort));
proto->Set(isolate, "setRequestHeader", FunctionTemplate::New(isolate, XMLHttpRequestSetRequestHeader));
proto->Set(isolate, "getAllResponseHeaders", FunctionTemplate::New(isolate, XMLHttpRequestGetAllResponseHeaders));
proto->Set(isolate, "getResponseHeader", FunctionTemplate::New(isolate, XMLHttpRequestGetResponseHeader));
Local<Function> xhrFunc = xhrTpl->GetFunction(isolate->GetCurrentContext()).ToLocalChecked();
xhrFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "UNSENT").ToLocalChecked(), Integer::New(isolate, 0));
xhrFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "OPENED").ToLocalChecked(), Integer::New(isolate, 1));
xhrFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "HEADERS_RECEIVED").ToLocalChecked(), Integer::New(isolate, 2));
xhrFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "LOADING").ToLocalChecked(), Integer::New(isolate, 3));
xhrFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "DONE").ToLocalChecked(), Integer::New(isolate, 4));
global->Set(isolate, "XMLHttpRequest", xhrTpl);
}
#endif