#ifdef WITH_NETWORKING #include "httprequest.h" #include "connection_pool.h" #include "global_thread_pool.h" #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #define NOMINMAX #include #include #include #pragma comment(lib, "ws2_32.lib") #else #include #include #include #include #include #include #include #include #define SOCKET int #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #define closesocket close #endif #ifdef WITH_SSL #ifdef _WIN32 #define SECURITY_WIN32 #include #include #include #include #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "secur32.lib") #else #include #include #include #include #include #include #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> active_requests; static std::atomic next_request_id{1}; static std::atomic 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(isolate, getGlobalContext()); active_requests[id] = std::move(client); return id; } HttpRequestClient::HttpRequestClient(Isolate* isolate, Global* 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 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 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 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 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 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 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 callback) { on_ready_state_change_.Reset(isolate_, callback); } void HttpRequestClient::setOnLoad(Local callback) { on_load_.Reset(isolate_, callback); } void HttpRequestClient::setOnError(Local callback) { on_error_.Reset(isolate_, callback); } void HttpRequestClient::setOnTimeout(Local callback) { on_timeout_.Reset(isolate_, callback); } void HttpRequestClient::setOnAbort(Local callback) { on_abort_.Reset(isolate_, callback); } void HttpRequestClient::setOnProgress(Local callback) { on_progress_.Reset(isolate_, callback); } void HttpRequestClient::setOnLoadStart(Local callback) { on_load_start_.Reset(isolate_, callback); } void HttpRequestClient::setOnLoadEnd(Local 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(sock); if (!performSSLHandshake(static_cast(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(request_data.length())); } else { bytes_sent = ::send(sock, request_data.c_str(), static_cast(request_data.length()), 0); } #else bytes_sent = ::send(sock, request_data.c_str(), static_cast(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 lock(event_queue_mutex_); event_queue_.push(Event(HTTP_READY_STATE_CHANGE)); } void HttpRequestClient::handleLoad() { std::lock_guard lock(event_queue_mutex_); event_queue_.push(Event(HTTP_LOAD)); } void HttpRequestClient::handleError(const std::string& error) { std::lock_guard lock(event_queue_mutex_); event_queue_.push(Event(HTTP_ERROR, error)); } void HttpRequestClient::handleTimeout() { std::lock_guard lock(event_queue_mutex_); event_queue_.push(Event(HTTP_TIMEOUT)); } void HttpRequestClient::handleAbort() { std::lock_guard lock(event_queue_mutex_); event_queue_.push(Event(HTTP_ABORT)); } void HttpRequestClient::handleProgress() { std::lock_guard lock(event_queue_mutex_); event_queue_.push(Event(HTTP_PROGRESS)); } void HttpRequestClient::handleLoadStart() { std::lock_guard lock(event_queue_mutex_); event_queue_.push(Event(HTTP_LOAD_START)); } void HttpRequestClient::handleLoadEnd() { std::lock_guard 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 = global_context_->Get(isolate_); Context::Scope context_scope(context); Local event = Object::New(isolate_); event->Set(context, String::NewFromUtf8(isolate_, "message").ToLocalChecked(), String::NewFromUtf8(isolate_, error.c_str()).ToLocalChecked()); Local 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 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& callback, int argc, Local argv[]) { if (callback.IsEmpty()) return; Locker locker{isolate_}; Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); Local context = global_context_->Get(isolate_); Context::Scope context_scope(context); TryCatch try_catch(isolate_); Local func = callback.Get(isolate_); Local 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 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(UNISP_NAME_A), SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, NULL, reinterpret_cast(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(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(ssl_context_handle_)); delete reinterpret_cast(ssl_context_handle_); ssl_context_handle_ = nullptr; } if (ssl_cred_handle_) { FreeCredentialsHandle(reinterpret_cast(ssl_cred_handle_)); delete reinterpret_cast(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 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(ssl_cred_handle_), nullptr, // phContext = NULL for first call const_cast(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(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(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(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(ssl_cred_handle_), reinterpret_cast(ssl_context_handle_), const_cast(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(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(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(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(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(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(bytes_to_copy)); if (extra_buffer && extra_buffer->cbBuffer > 0) { std::memmove(ssl_buffer_.data(), extra_buffer->pvBuffer, static_cast(extra_buffer->cbBuffer)); ssl_buffer_.resize(static_cast(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(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(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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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 buffer = ArrayBuffer::New(isolate, 0); args.GetReturnValue().Set(buffer); } else { size_t length = responseText.length(); std::unique_ptr backing = v8::ArrayBuffer::NewBackingStore(isolate, length); memcpy(backing->Data(), responseText.c_str(), length); Local 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 buffer = ArrayBuffer::New(isolate, 0); args.GetReturnValue().Set(buffer); } } void runt_httprequest_get_response_url(const FunctionCallbackInfo& 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& 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& 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(type)); } } void runt_httprequest_set_with_credentials(const FunctionCallbackInfo& 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& 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 callback = Local::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& 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 callback = Local::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& 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 callback = Local::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& 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 callback = Local::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& 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 callback = Local::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& 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 callback = Local::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& 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 callback = Local::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& 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 callback = Local::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& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local 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 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& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local 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 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& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local self = args.Holder(); Local 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& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local self = args.Holder(); Local 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& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local 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 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& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local self = args.Holder(); Local 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& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local self = args.Holder(); if (args.Length() < 1 || !args[0]->IsString()) { args.GetReturnValue().SetNull(); return; } Local 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& global) { Local xhrTpl = FunctionTemplate::New(isolate, XMLHttpRequestConstructor); xhrTpl->SetClassName(String::NewFromUtf8(isolate, "XMLHttpRequest").ToLocalChecked()); xhrTpl->InstanceTemplate()->SetInternalFieldCount(1); Local 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 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