Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ else()
message(STATUS "idasql: Fetching libxsql from GitHub...")
FetchContent_Declare(libxsql
GIT_REPOSITORY https://github.com/0xeb/libxsql.git
GIT_TAG v1.0.7
GIT_TAG v1.0.8
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(libxsql)
Expand Down
53 changes: 45 additions & 8 deletions src/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,16 @@ static void run_repl(idasql::Database& db) {
}

// SQL executor - called on main thread via run_until_stopped()
idasql::HTTPQueryCallback sql_cb = [&db](const std::string& sql) -> std::string {
return query_result_to_json(db, sql);
};
idasql::HTTPStatementExecutor sql_cb =
[&db](const std::string& stmt, xsql::ScriptStatementResult& out) {
idasql::QueryResult r = db.query(stmt);
out.columns = r.columns;
out.rows.reserve(r.rows.size());
for (const auto& row : r.rows) out.rows.push_back(row.values);
out.elapsed_ms = static_cast<double>(r.elapsed_ms);
out.success = r.success;
out.error = r.error;
};

// Start with use_queue=true (CLI mode)
int port = g_repl_http_server->start(req_port, sql_cb, addr, true);
Expand Down Expand Up @@ -578,6 +585,8 @@ static void http_signal_handler(int) {
// Command queue for main-thread execution (needed for Hex-Rays decompiler)
struct HttpPendingCommand {
std::string sql;
xsql::ScriptOptions opts; // continue_on_error / include_sql from query string
std::string format = "json"; // json | text | csv | tsv
std::string result;
bool started = false;
bool canceled = false;
Expand All @@ -592,13 +601,17 @@ static std::deque<std::shared_ptr<HttpPendingCommand>> g_http_pending_commands;
static std::atomic<bool> g_http_running{false};

// Queue a command and wait for main thread to execute it
static std::string http_queue_and_wait(const std::string& sql) {
static std::string http_queue_and_wait(const std::string& sql,
const xsql::ScriptOptions& opts = {},
const std::string& format = "json") {
if (!g_http_running.load()) {
return xsql::json{{"success", false}, {"error", "Server not running"}}.dump();
}

auto cmd = std::make_shared<HttpPendingCommand>();
cmd->sql = sql;
cmd->opts = opts;
cmd->format = format;
cmd->completed = false;

{
Expand Down Expand Up @@ -746,8 +759,26 @@ static int run_http_mode(idasql::Database& db, int port, const std::string& bind
res.set_content(xsql::json{{"success", false}, {"error", "Empty query"}}.dump(), "application/json");
return;
}
// Queue command for main thread execution
res.set_content(http_queue_and_wait(req.body), "application/json");
// Parse query-string options (same surface as the libxsql thinclient).
xsql::ScriptOptions opts;
{
auto it = req.params.find("continue_on_error");
if (it != req.params.end() && it->second == "1") opts.continue_on_error = true;
auto incl = req.params.find("include_sql");
if (incl != req.params.end() && incl->second == "1") opts.include_sql = true;
}
std::string format = "json";
{
auto it = req.params.find("format");
if (it != req.params.end() && !it->second.empty()) format = it->second;
}
// Queue command for main thread execution (Hex-Rays thread affinity).
std::string body = http_queue_and_wait(req.body, opts, format);
const char* ctype = format == "text" ? "text/plain"
: format == "csv" ? "text/csv"
: format == "tsv" ? "text/tab-separated-values"
: "application/json";
res.set_content(body, ctype);
});

// GET /status - Also needs main thread for db.query()
Expand Down Expand Up @@ -853,8 +884,14 @@ static int run_http_mode(idasql::Database& db, int port, const std::string& bind
}

if (should_execute) {
// Execute query on main thread - safe for Hex-Rays decompiler
std::string result = query_result_to_json(db, cmd->sql);
// Execute on main thread (safe for Hex-Rays) with the request's
// options, then render per the requested format.
xsql::ScriptResult sr = idasql::run_sql_script(db, cmd->sql, cmd->opts);
std::string result =
cmd->format == "text" ? xsql::script_result_to_text(sr)
: cmd->format == "csv" ? xsql::script_result_to_csv(sr)
: cmd->format == "tsv" ? xsql::script_result_to_tsv(sr)
: xsql::script_result_to_json(sr, cmd->opts.include_sql);
{
std::lock_guard<std::mutex> lock(cmd->done_mutex);
cmd->result = std::move(result);
Expand Down
40 changes: 30 additions & 10 deletions src/common/http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,13 @@ static std::string build_http_help_text() {
return out.str();
}

int IDAHTTPServer::start(int port, HTTPQueryCallback query_cb,
const std::string& bind_addr, bool use_queue) {
if (impl_ && impl_->is_running()) {
return impl_->port();
}

bind_addr_ = bind_addr.empty() ? "127.0.0.1" : bind_addr;

static xsql::thinclient::http_query_server_config make_idasql_config(
int port, const std::string& bind_addr, bool use_queue) {
xsql::thinclient::http_query_server_config config;
config.tool_name = "idasql";
config.help_text = build_http_help_text();
config.port = port;
config.bind_address = bind_addr_;
config.query_fn = std::move(query_cb);
config.bind_address = bind_addr;
config.use_queue = use_queue;
config.queue_admission_timeout_ms_fn = []() {
return idasql::runtime_settings().queue_admission_timeout_ms();
Expand All @@ -74,7 +67,34 @@ int IDAHTTPServer::start(int port, HTTPQueryCallback query_cb,
{"hints_enabled", settings.hints_enabled ? 1 : 0}
};
};
return config;
}

// Legacy: whole-script JSON callback (used by the in-process plugin).
int IDAHTTPServer::start(int port, HTTPQueryCallback query_cb,
const std::string& bind_addr, bool use_queue) {
if (impl_ && impl_->is_running()) {
return impl_->port();
}
bind_addr_ = bind_addr.empty() ? "127.0.0.1" : bind_addr;
auto config = make_idasql_config(port, bind_addr_, use_queue);
config.query_fn = std::move(query_cb);
impl_ = std::make_unique<xsql::thinclient::http_query_server>(config);
return impl_->start();
}

// Preferred: single-statement executor (CLI). Enables continue_on_error/
// include_sql and round-trip-free format=.
int IDAHTTPServer::start(int port, HTTPStatementExecutor executor,
const std::string& bind_addr, bool use_queue,
const std::string& auth_token) {
if (impl_ && impl_->is_running()) {
return impl_->port();
}
bind_addr_ = bind_addr.empty() ? "127.0.0.1" : bind_addr;
auto config = make_idasql_config(port, bind_addr_, use_queue);
config.statement_executor = std::move(executor);
if (!auth_token.empty()) config.auth_token = auth_token;
impl_ = std::make_unique<xsql::thinclient::http_query_server>(config);
return impl_->start();
}
Expand Down
17 changes: 16 additions & 1 deletion src/common/http_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,17 @@

namespace idasql {

// Callback for handling SQL queries
// Legacy callback: SQL script in, JSON string out. Used by the in-process
// plugin, which marshals whole-script execution to IDA's main thread via
// execute_sync and returns the serialized envelope itself.
using HTTPQueryCallback = std::function<std::string(const std::string& sql)>;

// Preferred path (CLI): a single-statement executor. The thinclient owns
// multi-statement orchestration, query-string options (continue_on_error/
// include_sql), and output formatting (json/text/csv/tsv) from the ScriptResult.
using HTTPStatementExecutor =
std::function<void(const std::string& sql, xsql::ScriptStatementResult& out)>;

class IDAHTTPServer {
public:
IDAHTTPServer() = default;
Expand All @@ -54,6 +62,13 @@ class IDAHTTPServer {
const std::string& bind_addr = "127.0.0.1",
bool use_queue = false);

// Preferred overload: drive the server with a single-statement executor
// (enables continue_on_error/include_sql and round-trip-free format=).
int start(int port, HTTPStatementExecutor executor,
const std::string& bind_addr = "127.0.0.1",
bool use_queue = false,
const std::string& auth_token = "");

/**
* Block until server stops, processing commands on the calling thread.
* Only needed when use_queue=true (CLI mode).
Expand Down
Loading