2387 lines
96 KiB
C++
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
|