mirror of
https://github.com/radareorg/r2ai.git
synced 2026-11-10 04:01:27 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f15ec6c73 | ||
|
|
68baac9911 | ||
|
|
54a280952e | ||
|
|
efc2368af1 | ||
|
|
28ddcbf638 | ||
|
|
85e9ed5c65 |
No files matched your search
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,6 +1,6 @@
|
||||
(function () {
|
||||
// Constants and configuration
|
||||
const VERSION = "1.2.2";
|
||||
const VERSION = "1.2.4";
|
||||
const COMMAND = "decai";
|
||||
const DEFAULT_PROMPT =
|
||||
"Transform this pseudocode and respond ONLY with plain code (NO explanations, comments or markdown), Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:', Reduce lines of code and fit everything in a single function, Remove all dead code";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Automatically suggest a better name for this function'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: afv;r2ai -d
|
||||
if-empty: exit
|
||||
---
|
||||
Suggest a better name for this function. Output only the suggested function name (must be one alphanumeric word) prefixed with `'afn '. Check the code below:
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Automatically suggest a better name for this function
|
||||
Author: pancake
|
||||
Command: afv;r2ai -d
|
||||
Prompt: Suggest a better name for this function. Output only the suggested function name (must be one alphanumeric word) prefixed with `'afn '. Check the code below:
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Augmented decompilation based on LLM'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: pdc
|
||||
if-empty: exit
|
||||
---
|
||||
$(-e r2ai.prompt). Translate the code into $(-e r2ai.lang) programming language.
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Augmented decompilation based on LLM
|
||||
Author: pancake
|
||||
Command: pdc
|
||||
Prompt: $(-e r2ai.prompt). Translate the code into $(-e r2ai.lang) programming language.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Find and explain devices used'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: izqq~^/dev
|
||||
if-empty: exit
|
||||
---
|
||||
explain in one line the purpose of each of these devices
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Find and explain devices used
|
||||
Author: pancake
|
||||
Command: izqq~^/dev
|
||||
Prompt: explain in one line the purpose of each of these devices
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'List libraries loaded with dlopen'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: iiq~dlopen
|
||||
if-empty: This binary is not using dlopen
|
||||
---
|
||||
Some libraries are loaded
|
||||
@@ -1,6 +0,0 @@
|
||||
Title: List libraries loaded with dlopen
|
||||
Description: Some libraries are loaded
|
||||
Requires: analysis
|
||||
Command: iiq~dlopen
|
||||
If-Empty: This binary is not using dlopen
|
||||
If-Command: e emu.str=true;pd--5@@c:axtq sym.imp.dlopen
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Explain the current function'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: r2ai -d
|
||||
if-empty: exit
|
||||
---
|
||||
Analyze function calls, comments and strings, ignore registers and memory accesses. Considering the references and involved loops; explain the purpose of this function in one or two short sentences. Output must be only the translation of the explanation in $(-e r2ai.hlang)
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Explain the current function
|
||||
Author: pancake
|
||||
Command: r2ai -d
|
||||
Prompt: Analyze function calls, comments and strings, ignore registers and memory accesses. Considering the references and involved loops; explain the purpose of this function in one or two short sentences. Output must be only the translation of the explanation in $(-e r2ai.hlang)
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Group imports by Libraries'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: iiq;il
|
||||
if-empty: exit
|
||||
---
|
||||
Group the imports that belong to each library and describe its purpose and usage scope
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Group imports by Libraries
|
||||
Author: pancake
|
||||
Commands: iiq;il
|
||||
Query: Group the imports that belong to each library and describe its purpose and usage scope
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Suggest an improved function signature'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: ?e variables;afv;?e function name:;afd;?e current signature:?e afs
|
||||
if-empty: exit
|
||||
---
|
||||
Analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the resturn. Do NOT print the function body. Output must be *ONLY* the function signature prefixed with the `'afs ` text
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Suggest an improved function signature
|
||||
Author: pancake
|
||||
Command: ?e variables;afv;?e function name:;afd;?e current signature:?e afs
|
||||
Prompt: Analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the resturn. Do NOT print the function body. Output must be *ONLY* the function signature prefixed with the `'afs ` text
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Better variable names'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: afv
|
||||
if-empty: exit
|
||||
---
|
||||
Output only the radare2 command without markdown, guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Better variable names
|
||||
Author: pancake
|
||||
Command: afv
|
||||
Prompt: Output only the radare2 command without markdown, guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: 'Find vulnerabilities or bugs in the current function'
|
||||
version: 0.1
|
||||
author: pancake
|
||||
command: pdc
|
||||
if-empty: exit
|
||||
---
|
||||
Find vulnerabilities, do not show the code, only a short summary of the resulting analysis report including suggested good practices to address the problem and a practical exploit example.
|
||||
@@ -1,4 +0,0 @@
|
||||
Title: Find vulnerabilities or bugs in the current function
|
||||
Author: pancake
|
||||
Command: pdc
|
||||
Prompt: Find vulnerabilities, do not show the code, only a short summary of the resulting analysis report including suggested good practices to address the problem and a practical exploit example.
|
||||
+2
-2
@@ -45,7 +45,7 @@ OBJS+=vdb.o
|
||||
OBJS+=tools.o
|
||||
OBJS+=messages.o
|
||||
OBJS+=anthropic.o
|
||||
OBJS+=r2ai_http.o
|
||||
OBJS+=http.o
|
||||
OBJS+=openai.o
|
||||
OBJS+=markdown.o
|
||||
OBJS+=query.o
|
||||
@@ -66,7 +66,7 @@ doc-install:
|
||||
vdb.o: vdb_embed.inc.c
|
||||
|
||||
fmt format indent:
|
||||
clang-format-radare2 *.c
|
||||
clang-format-radare2 *.c http/*.c
|
||||
|
||||
user-install: user-uninstall
|
||||
mkdir -p $(R2USRPLUGDIR)
|
||||
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
#include "r2ai.h"
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
|
||||
// Global flag for tracking interrupt status
|
||||
static volatile sig_atomic_t r2ai_http_interrupted = 0;
|
||||
|
||||
// Helper function to get HTTP configuration with defaults
|
||||
static R2AI_HttpConfig get_http_config(RCore *core) {
|
||||
R2AI_HttpConfig config = { 120, 10, 30 };
|
||||
if (core) {
|
||||
int t = r_config_get_i (core->config, "r2ai.http.timeout");
|
||||
if (t > 0) {
|
||||
config.timeout = t;
|
||||
}
|
||||
int mr = r_config_get_i (core->config, "r2ai.http.max_retries");
|
||||
if (mr >= 0) {
|
||||
config.max_retries = mr;
|
||||
}
|
||||
int mb = r_config_get_i (core->config, "r2ai.http.max_backoff");
|
||||
if (mb > 0) {
|
||||
config.max_backoff = mb;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// Signal handler for timeout (SIGALRM)
|
||||
static void r2ai_http_sigint_handler(int sig) {
|
||||
if (sig == SIGALRM) {
|
||||
r2ai_http_interrupted = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Portable break callback for r_cons_break (handles SIGINT)
|
||||
static void r2ai_http_break_callback(void *user) {
|
||||
(void)user;
|
||||
r2ai_http_interrupted = 1;
|
||||
}
|
||||
|
||||
// Function pointer type for HTTP request implementations
|
||||
typedef HttpResponse(*HttpRequestFunc)(const HTTPRequest *request);
|
||||
|
||||
// Simple retry sleep
|
||||
static void sleep_retry(int retry_count, int max_sleep_seconds) {
|
||||
int delay_seconds = retry_count + 1; // sleep 1, 2, 3, ... seconds
|
||||
if (delay_seconds > max_sleep_seconds) {
|
||||
delay_seconds = max_sleep_seconds;
|
||||
}
|
||||
r_sys_sleep (delay_seconds);
|
||||
}
|
||||
|
||||
// Generic HTTP request with retry logic
|
||||
static HttpResponse r2ai_http_request_with_retry(HttpRequestFunc func, const HTTPRequest *request, RCore *core) {
|
||||
// Install portable break handler for interruption
|
||||
if (core && core->cons) {
|
||||
r_cons_break_push (core->cons, r2ai_http_break_callback, NULL);
|
||||
}
|
||||
|
||||
// Reset interrupt flag
|
||||
r2ai_http_interrupted = 0;
|
||||
|
||||
// Retry loop
|
||||
HttpResponse result = { 0 };
|
||||
int retry_count = 0;
|
||||
bool success = false;
|
||||
|
||||
while (!success && retry_count <= request->config.max_retries && !r2ai_http_interrupted) {
|
||||
result = func (request);
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
if (result.body) {
|
||||
free (result.body);
|
||||
}
|
||||
result.body = NULL;
|
||||
result.code = -1;
|
||||
break; // Exit the retry loop
|
||||
}
|
||||
|
||||
// Check for rate limiting or server errors (429, 500, 502, 503, 504)
|
||||
if (result.code > 0) {
|
||||
if (result.code == 429 || (result.code >= 500 && result.code < 600)) {
|
||||
R_LOG_WARN ("Server returned %d response code", result.code);
|
||||
free (result.body);
|
||||
result.body = NULL;
|
||||
result.code = 0;
|
||||
|
||||
if (retry_count < request->config.max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after error...", retry_count, request->config.max_retries);
|
||||
sleep_retry (retry_count, request->config.max_backoff);
|
||||
continue;
|
||||
}
|
||||
result.code = -1;
|
||||
break; // Exit the retry loop after max retries
|
||||
}
|
||||
}
|
||||
|
||||
// Check for other failures
|
||||
if (result.code <= 0 || !result.body) {
|
||||
if (result.body) {
|
||||
free (result.body);
|
||||
}
|
||||
result.body = NULL;
|
||||
result.code = -1;
|
||||
if (retry_count < request->config.max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, request->config.max_retries);
|
||||
sleep_retry (retry_count, request->config.max_backoff);
|
||||
continue;
|
||||
}
|
||||
break; // Exit the retry loop after max retries
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
|
||||
// Restore the original break handler
|
||||
if (core && core->cons) {
|
||||
r_cons_break_pop (core->cons);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#include "http/libcurl.inc.c"
|
||||
#include "http/pwsh.inc.c"
|
||||
#include "http/curl.inc.c"
|
||||
#include "http/r2.inc.c"
|
||||
|
||||
static HttpRequestFunc select_backend(const char *backend, bool is_post) {
|
||||
HttpRequestFunc func = NULL;
|
||||
// Select the appropriate backend function
|
||||
if (!strcmp (backend, "auto")) {
|
||||
// Auto-select the best available backend
|
||||
#if defined(_WIN32)
|
||||
backend = "pwsh";
|
||||
#elif USE_LIBCURL && HAVE_LIBCURL
|
||||
backend = "libcurl";
|
||||
#else
|
||||
backend = "system";
|
||||
#endif
|
||||
R_LOG_DEBUG ("Auto-selected %s backend", backend);
|
||||
}
|
||||
if (!strcmp (backend, "system") || !strcmp (backend, "curl")) {
|
||||
func = is_post? system_curl_post: system_curl_get;
|
||||
} else if (!strcmp (backend, "libcurl")) {
|
||||
#if USE_LIBCURL && HAVE_LIBCURL
|
||||
func = is_post? curl_http_post: curl_http_get;
|
||||
#else
|
||||
R_LOG_WARN ("LibCurl requested but not available, falling back to socket implementation");
|
||||
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
|
||||
#endif
|
||||
} else if (!strcmp (backend, "socket")) {
|
||||
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
|
||||
} else if (!strcmp (backend, "pwsh") || !strcmp (backend, "powershell")) {
|
||||
#if defined(_WIN32)
|
||||
func = is_post? windows_http_post: windows_http_get;
|
||||
#else
|
||||
R_LOG_WARN ("powershell is only available on Windows");
|
||||
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
|
||||
#endif
|
||||
} else if (!strcmp (backend, "r2curl")) {
|
||||
r_sys_setenv ("R2_CURL", "1");
|
||||
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
// Generic HTTP request function that handles backend selection
|
||||
static HttpResponse r2ai_http_request(const char *method, RCore *core, const char *url, const char *headers[], const char *data) {
|
||||
(void)method;
|
||||
bool is_post = (data != NULL);
|
||||
|
||||
HTTPRequest request = {
|
||||
.config = get_http_config (core),
|
||||
.url = url,
|
||||
.data = data,
|
||||
.headers = headers
|
||||
};
|
||||
|
||||
const char *backend = r_config_get (core->config, "r2ai.http.backend");
|
||||
HttpRequestFunc func = select_backend (backend, is_post);
|
||||
if (func) {
|
||||
return r2ai_http_request_with_retry (func, &request, core);
|
||||
}
|
||||
R_LOG_ERROR ("Cannot find a valid http backend");
|
||||
return (HttpResponse){ .body = NULL, .code = -1, .length = 0 };
|
||||
}
|
||||
|
||||
R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen) {
|
||||
HttpResponse response = r2ai_http_request ("POST", core, url, headers, data);
|
||||
if (response.code <= 0) {
|
||||
if (response.body) {
|
||||
free (response.body);
|
||||
}
|
||||
if (code) {
|
||||
*code = response.code;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (code) {
|
||||
*code = response.code;
|
||||
}
|
||||
if (rlen) {
|
||||
*rlen = response.length;
|
||||
}
|
||||
return response.body;
|
||||
}
|
||||
|
||||
R_API char *r2ai_http_get(RCore *core, const char *url, const char *headers[], int *code, int *rlen) {
|
||||
HttpResponse response = r2ai_http_request ("GET", core, url, headers, NULL);
|
||||
if (response.code <= 0) {
|
||||
if (response.body) {
|
||||
free (response.body);
|
||||
}
|
||||
if (code) {
|
||||
*code = response.code;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (code) {
|
||||
*code = response.code;
|
||||
}
|
||||
if (rlen) {
|
||||
*rlen = response.length;
|
||||
}
|
||||
return response.body;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
static HttpResponse build_and_execute_curl(const char *cmd_start, const HTTPRequest *request, const char *input_data) {
|
||||
HttpResponse error = { .code = -1 };
|
||||
int timeout = request->config.timeout;
|
||||
if (!request->url) {
|
||||
return error;
|
||||
}
|
||||
|
||||
// Compose curl command
|
||||
RStrBuf *cmd = r_strbuf_new (cmd_start);
|
||||
|
||||
// Add timeout
|
||||
r_strbuf_appendf (cmd, " --connect-timeout %d --max-time %d", 10, timeout);
|
||||
|
||||
// Add headers
|
||||
if (request->headers) {
|
||||
for (int i = 0; request->headers[i] != NULL; i++) {
|
||||
r_strbuf_appendf (cmd, " -H '%s'", request->headers[i]);
|
||||
}
|
||||
}
|
||||
r_strbuf_appendf (cmd, " -w '\\n%%{http_code}' '%s'", request->url);
|
||||
char *cmd_str = r_strbuf_drain (cmd);
|
||||
R_LOG_DEBUG ("Running system curl: %s", cmd_str);
|
||||
char *response = r_sys_cmd_str (cmd_str, input_data, NULL);
|
||||
|
||||
free (cmd_str);
|
||||
|
||||
if (!response) {
|
||||
return error;
|
||||
}
|
||||
|
||||
// Parse the response: body\ncode
|
||||
char *last_nl = (char *)r_str_rchr (response, NULL, '\n');
|
||||
int code = -1;
|
||||
char *body = response;
|
||||
if (last_nl) {
|
||||
*last_nl = '\0';
|
||||
code = atoi (last_nl + 1);
|
||||
if (code == 0) {
|
||||
code = -1; // Invalid code
|
||||
}
|
||||
} else {
|
||||
// No newline, assume 200 if response
|
||||
code = 200;
|
||||
}
|
||||
|
||||
return (HttpResponse){ .body = body, .code = code, .length = strlen (body) };
|
||||
}
|
||||
|
||||
HttpResponse system_curl_get(const HTTPRequest *request) {
|
||||
R_LOG_DEBUG ("Using system curl GET");
|
||||
return build_and_execute_curl ("curl -s", request, NULL);
|
||||
}
|
||||
|
||||
HttpResponse system_curl_post(const HTTPRequest *request) {
|
||||
HttpResponse error = { .code = -1 };
|
||||
if (!request->url || !request->data) {
|
||||
return error;
|
||||
}
|
||||
|
||||
R_LOG_DEBUG ("Using system curl POST without files");
|
||||
HttpResponse res = build_and_execute_curl ("curl -s -X POST -d @-", request, request->data);
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
#if HAVE_LIBCURL
|
||||
#include <curl/curl.h>
|
||||
|
||||
// Struct to store response data
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t size;
|
||||
} CurlResponse;
|
||||
|
||||
// Callback function for curl to write response data
|
||||
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||
// Check if we've been interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
return 0; // Return 0 to abort the transfer
|
||||
}
|
||||
|
||||
size_t realsize = size * nmemb;
|
||||
CurlResponse *mem = (CurlResponse *)userp;
|
||||
|
||||
char *ptr = realloc (mem->data, mem->size + realsize + 1);
|
||||
if (!ptr) {
|
||||
R_LOG_ERROR ("Not enough memory for curl response");
|
||||
return 0; // Out of memory
|
||||
}
|
||||
|
||||
mem->data = ptr;
|
||||
memcpy (&(mem->data[mem->size]), contents, realsize);
|
||||
mem->size += realsize;
|
||||
mem->data[mem->size] = 0;
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
// Curl progress callback function to handle interrupts
|
||||
static int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
|
||||
return r2ai_http_interrupted? 1: 0;
|
||||
}
|
||||
|
||||
static HttpResponse curl_http_request(const HTTPRequest *request, bool is_post) {
|
||||
HttpResponse error = { .code = -1 };
|
||||
if (!request->url || (is_post && !request->data)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
CURL *curl;
|
||||
CURLcode res;
|
||||
struct curl_slist *curl_headers = NULL;
|
||||
CurlResponse response = { 0 };
|
||||
|
||||
// Initialize response
|
||||
response.data = malloc (1);
|
||||
if (!response.data) {
|
||||
return error;
|
||||
}
|
||||
response.data[0] = '\0';
|
||||
response.size = 0;
|
||||
|
||||
curl = curl_easy_init ();
|
||||
if (!curl) {
|
||||
free (response.data);
|
||||
return error;
|
||||
}
|
||||
|
||||
// Set URL
|
||||
curl_easy_setopt (curl, CURLOPT_URL, request->url);
|
||||
|
||||
// Set method-specific options
|
||||
if (is_post) {
|
||||
curl_easy_setopt (curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, request->data);
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long)strlen (request->data));
|
||||
} else {
|
||||
curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L);
|
||||
}
|
||||
|
||||
// Set headers if provided
|
||||
if (request->headers) {
|
||||
for (int i = 0; request->headers[i] != NULL; i++) {
|
||||
curl_headers = curl_slist_append (curl_headers, request->headers[i]);
|
||||
}
|
||||
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, curl_headers);
|
||||
}
|
||||
|
||||
// Set write callback
|
||||
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *)&response);
|
||||
|
||||
// Set progress callback to handle interrupts
|
||||
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt (curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
|
||||
curl_easy_setopt (curl, CURLOPT_XFERINFODATA, NULL);
|
||||
|
||||
// Set timeout options
|
||||
curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 10L); // 10 seconds connect timeout
|
||||
curl_easy_setopt (curl, CURLOPT_TIMEOUT, (long)request->config.timeout); // Use configured timeout
|
||||
|
||||
// Follow redirects
|
||||
curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
// Perform the request
|
||||
res = curl_easy_perform (curl);
|
||||
|
||||
// Check for interruption
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
free (response.data);
|
||||
curl_slist_free_all (curl_headers);
|
||||
curl_easy_cleanup (curl);
|
||||
return error;
|
||||
}
|
||||
|
||||
// Get response code
|
||||
long http_code;
|
||||
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
// Check for errors
|
||||
if (res != CURLE_OK) {
|
||||
R_LOG_ERROR ("curl_easy_perform () failed: %s", curl_easy_strerror (res));
|
||||
free (response.data);
|
||||
curl_slist_free_all (curl_headers);
|
||||
curl_easy_cleanup (curl);
|
||||
return error;
|
||||
}
|
||||
|
||||
// If we get here, the request was successful
|
||||
// Cleanup
|
||||
curl_slist_free_all (curl_headers);
|
||||
curl_easy_cleanup (curl);
|
||||
|
||||
return (HttpResponse){ .body = response.data, .code = (int)http_code, .length = response.size };
|
||||
}
|
||||
|
||||
HttpResponse curl_http_get(const HTTPRequest *request) {
|
||||
return curl_http_request (request, false);
|
||||
}
|
||||
|
||||
HttpResponse curl_http_post(const HTTPRequest *request) {
|
||||
return curl_http_request (request, true);
|
||||
}
|
||||
#endif // HAVE_LIBCURL
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
// Helper to append headers to PowerShell command
|
||||
static void append_headers_to_cmd(RStrBuf *cmd, const char *const *headers) {
|
||||
r_strbuf_appendf (cmd, "$ProgressPreference='SilentlyContinue';$headers=@{");
|
||||
if (headers) {
|
||||
for (int i = 0; headers[i]; i++) {
|
||||
char *header = strdup (headers[i]);
|
||||
char *colon = strchr (header, ':');
|
||||
if (colon) {
|
||||
*colon = '\0';
|
||||
char *key = r_str_trim_dup (header);
|
||||
char *value = r_str_trim_dup (colon + 1);
|
||||
r_strbuf_appendf (cmd, "'%s'='%s';", key, value);
|
||||
free (key);
|
||||
free (value);
|
||||
}
|
||||
free (header);
|
||||
}
|
||||
}
|
||||
r_strbuf_appendf (cmd, "};");
|
||||
}
|
||||
|
||||
// Helper to parse PowerShell response
|
||||
static HttpResponse parse_powershell_response(char *full_response) {
|
||||
if (full_response) {
|
||||
char *newline = strchr (full_response, '\n');
|
||||
if (newline) {
|
||||
*newline = '\0';
|
||||
int code = atoi (full_response);
|
||||
char *body = newline + 1;
|
||||
char *body_copy = strdup (body);
|
||||
size_t length = strlen (body);
|
||||
free (full_response);
|
||||
return (HttpResponse){ .body = body_copy, .code = code, .length = length };
|
||||
} else {
|
||||
return (HttpResponse){ .body = full_response, .code = 200, .length = strlen (full_response) };
|
||||
}
|
||||
}
|
||||
return (HttpResponse){ .code = -1 };
|
||||
}
|
||||
/**
|
||||
* Windows-specific HTTP POST using PowerShell
|
||||
*/
|
||||
HttpResponse windows_http_post(const HTTPRequest *request) {
|
||||
const char *url = request->url;
|
||||
const char *const *headers = request->headers;
|
||||
const char *data = request->data;
|
||||
int timeout = request->config.timeout;
|
||||
RStrBuf *cmd = r_strbuf_new ("powershell -Command \"");
|
||||
append_headers_to_cmd (cmd, headers);
|
||||
r_strbuf_appendf (cmd, "$body='%s';", data);
|
||||
r_strbuf_appendf (cmd, "try{$r=Invoke-WebRequest -Method Post -Uri '%s' -Headers $headers -Body $body -TimeoutSec %d;", url, timeout);
|
||||
r_strbuf_appendf (cmd, "Write-Host $r.StatusCode;$r.Content}catch{Write-Host 0;$_.Exception.Message}\"");
|
||||
char *cmd_str = r_strbuf_drain (cmd);
|
||||
char *full_response = r_sys_cmd_str (cmd_str, NULL, NULL);
|
||||
free (cmd_str);
|
||||
return parse_powershell_response (full_response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows-specific HTTP GET using PowerShell
|
||||
*/
|
||||
HttpResponse windows_http_get(const HTTPRequest *request) {
|
||||
const char *url = request->url;
|
||||
const char *const *headers = request->headers;
|
||||
int timeout = request->config.timeout;
|
||||
RStrBuf *cmd = r_strbuf_new ("powershell -Command \"");
|
||||
append_headers_to_cmd (cmd, headers);
|
||||
r_strbuf_appendf (cmd, "try{$r=Invoke-WebRequest -Method Get -Uri '%s' -Headers $headers -TimeoutSec %d;", url, timeout);
|
||||
r_strbuf_appendf (cmd, "Write-Host $r.StatusCode;$r.Content}catch{Write-Host 0;$_.Exception.Message}\"");
|
||||
char *cmd_str = r_strbuf_drain (cmd);
|
||||
char *full_response = r_sys_cmd_str (cmd_str, NULL, NULL);
|
||||
free (cmd_str);
|
||||
return parse_powershell_response (full_response);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
extern volatile sig_atomic_t r2ai_http_interrupted;
|
||||
extern void r2ai_http_sigint_handler(int sig);
|
||||
|
||||
// TODO: r_sys_setenv ("R2_CURL", "1"); // Ensure R2 uses system curl
|
||||
|
||||
// Socket implementation with interrupt handling and retry logic
|
||||
static HttpResponse socket_http_post_with_interrupt(const HTTPRequest *request) {
|
||||
// Set an alarm to limit the request time
|
||||
#if R2__UNIX__
|
||||
signal (SIGALRM, r2ai_http_sigint_handler);
|
||||
alarm (request->config.timeout); // Use configured timeout
|
||||
#endif
|
||||
|
||||
// Make the request
|
||||
int code = 0;
|
||||
int rlen = 0;
|
||||
char *result = r_socket_http_post (request->url, (const char **)request->headers, request->data, &code, &rlen);
|
||||
|
||||
// Clear the alarm
|
||||
#if R2__UNIX__
|
||||
alarm (0);
|
||||
#endif
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
free (result);
|
||||
return (HttpResponse){ .body = NULL, .code = -1, .length = 0 };
|
||||
}
|
||||
|
||||
return (HttpResponse){ .body = result, .code = code, .length = rlen };
|
||||
}
|
||||
|
||||
// Socket implementation for GET requests
|
||||
static HttpResponse socket_http_get_with_interrupt(const HTTPRequest *request) {
|
||||
// Set an alarm to limit the request time
|
||||
#if R2__UNIX__
|
||||
signal (SIGALRM, r2ai_http_sigint_handler);
|
||||
alarm (request->config.timeout); // Use configured timeout
|
||||
#endif
|
||||
// Make the request - use r_socket_http_get if available
|
||||
int code = 0;
|
||||
int rlen = 0;
|
||||
char *result = r_socket_http_get (request->url, (const char **)request->headers, &code, &rlen);
|
||||
#if R2__UNIX__
|
||||
alarm (0);
|
||||
#endif
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
free (result);
|
||||
return (HttpResponse){ .body = NULL, .code = -1, .length = 0 };
|
||||
}
|
||||
|
||||
return (HttpResponse){ .body = result, .code = code, .length = rlen };
|
||||
}
|
||||
@@ -150,7 +150,42 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCorePluginSession *cps, R2AIArgs args) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Return a malloc'd API key read from the environment or from ~/.r2ai.<provider>-key
|
||||
static char *r2ai_get_api_key_from_config(const char *provider) {
|
||||
char *config_file = r_file_abspath ("~/.config/r2ai/apikeys.txt");
|
||||
if (!r_file_exists (config_file)) {
|
||||
free (config_file);
|
||||
return NULL;
|
||||
}
|
||||
char *content = r_file_slurp (config_file, NULL);
|
||||
free (config_file);
|
||||
if (!content) {
|
||||
return NULL;
|
||||
}
|
||||
RList *lines = r_str_split_list (content, "\n", 0);
|
||||
free (content);
|
||||
if (!lines) {
|
||||
return NULL;
|
||||
}
|
||||
char *prefix = r_str_newf ("%s=", provider);
|
||||
char *key = NULL;
|
||||
RListIter *iter;
|
||||
char *line;
|
||||
r_list_foreach (lines, iter, line) {
|
||||
if (r_str_startswith (line, prefix)) {
|
||||
char *eq = strchr (line, '=');
|
||||
if (eq) {
|
||||
key = strdup (eq + 1);
|
||||
r_str_trim (key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
r_list_free (lines);
|
||||
free (prefix);
|
||||
return key;
|
||||
}
|
||||
|
||||
/* Return a malloc'd API key read from the env, ~/.config/r2ai/apikeys.txt, or ~/.r2ai.<provider>-key
|
||||
* Caller is responsible for freeing the returned string (or NULL if not found). */
|
||||
R_IPI char *r2ai_get_api_key(RCore *core, const char *provider) {
|
||||
(void)core;
|
||||
@@ -163,16 +198,19 @@ R_IPI char *r2ai_get_api_key(RCore *core, const char *provider) {
|
||||
api_key = s;
|
||||
} else {
|
||||
free (s);
|
||||
char *api_key_filename = r_str_newf ("~/.r2ai.%s-key", provider);
|
||||
char *absolute_apikey = r_file_abspath (api_key_filename);
|
||||
if (r_file_exists (absolute_apikey)) {
|
||||
api_key = r_file_slurp (absolute_apikey, NULL);
|
||||
if (api_key) {
|
||||
r_str_trim (api_key);
|
||||
api_key = r2ai_get_api_key_from_config (provider);
|
||||
if (!api_key) {
|
||||
char *api_key_filename = r_str_newf ("~/.r2ai.%s-key", provider);
|
||||
char *absolute_apikey = r_file_abspath (api_key_filename);
|
||||
if (r_file_exists (absolute_apikey)) {
|
||||
api_key = r_file_slurp (absolute_apikey, NULL);
|
||||
if (api_key) {
|
||||
r_str_trim (api_key);
|
||||
}
|
||||
}
|
||||
free (api_key_filename);
|
||||
free (absolute_apikey);
|
||||
}
|
||||
free (api_key_filename);
|
||||
free (absolute_apikey);
|
||||
}
|
||||
return api_key;
|
||||
}
|
||||
@@ -190,15 +228,13 @@ R_IPI const char *r2ai_get_provider_url(RCore *core, const char *provider) {
|
||||
if (r_str_startswith (host, "http")) {
|
||||
if (!strcmp (provider, "openai")) {
|
||||
return r_str_newf ("%s/v1", host);
|
||||
} else {
|
||||
return r_str_newf ("%s/api", host);
|
||||
}
|
||||
return r_str_newf ("%s/api", host);
|
||||
}
|
||||
if (!strcmp (provider, "openai")) {
|
||||
return r_str_newf ("http://%s/v1", host);
|
||||
} else {
|
||||
return r_str_newf ("http://%s/api", host);
|
||||
}
|
||||
return r_str_newf ("http://%s/api", host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,16 +247,9 @@ R_IPI RList *r2ai_fetch_available_models(RCore *core, const char *provider) {
|
||||
}
|
||||
|
||||
// Create models endpoint URL
|
||||
char *models_url = NULL;
|
||||
const R2AIProvider *prov = r2ai_get_provider (provider);
|
||||
if (prov && prov->uses_tags_endpoint) {
|
||||
models_url = r_str_newf ("%s/tags", purl);
|
||||
} else {
|
||||
models_url = r_str_newf ("%s/models", purl);
|
||||
}
|
||||
if (!models_url) {
|
||||
return NULL;
|
||||
}
|
||||
const bool usetags = (prov && prov->uses_tags_endpoint);
|
||||
char *models_url = r_str_newf ("%s/%s", purl, usetags? "tags": "models");
|
||||
|
||||
// Get API key for authentication (except for providers that don't require it)
|
||||
char *api_key = NULL;
|
||||
@@ -279,13 +308,9 @@ R_IPI RList *r2ai_fetch_available_models(RCore *core, const char *provider) {
|
||||
|
||||
RJson *json = r_json_parse (response);
|
||||
if (json) {
|
||||
const RJson *data;
|
||||
const R2AIProvider *prov = r2ai_get_provider (provider);
|
||||
if (prov && prov->uses_tags_endpoint) {
|
||||
data = r_json_get (json, "models");
|
||||
} else {
|
||||
data = r_json_get (json, "data");
|
||||
}
|
||||
const bool usetags = (prov && prov->uses_tags_endpoint);
|
||||
const RJson *data = r_json_get (json, usetags? "models": "data");
|
||||
|
||||
if (data && data->type == R_JSON_ARRAY) {
|
||||
const RJson *model_item = data->children.first;
|
||||
@@ -311,8 +336,7 @@ R_IPI RList *r2ai_fetch_available_models(RCore *core, const char *provider) {
|
||||
}
|
||||
|
||||
R_IPI void r2ai_list_providers(RCore *core, RStrBuf *sb) {
|
||||
size_t i;
|
||||
for (i = 0; r2ai_providers[i].name; i++) {
|
||||
for (size_t i = 0; r2ai_providers[i].name; i++) {
|
||||
if (sb) {
|
||||
if (i > 0) {
|
||||
r_strbuf_append (sb, ", ");
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
project('r2ai', 'c',
|
||||
version : '1.2.2',
|
||||
version : '1.2.4',
|
||||
default_options : ['warning_level=2', 'c_std=c11']
|
||||
)
|
||||
|
||||
@@ -194,7 +194,7 @@ srcs = files(
|
||||
'rawtools.c',
|
||||
'messages.c',
|
||||
'anthropic.c',
|
||||
'r2ai_http.c',
|
||||
'http.c',
|
||||
'openai.c',
|
||||
'markdown.c',
|
||||
)
|
||||
|
||||
+1
-39
@@ -71,45 +71,7 @@ static R2AIPrompt *parse_prompt_file(const char *filepath) {
|
||||
prompt->prompt = strdup (prompt_start);
|
||||
}
|
||||
} else {
|
||||
// Original parsing for .txt files
|
||||
RList *lines = r_str_split_list (content, "\n", -1);
|
||||
RListIter *iter;
|
||||
char *line;
|
||||
r_list_foreach (lines, iter, line) {
|
||||
r_str_trim (line);
|
||||
if (*line == '#' || *line == 0) {
|
||||
continue;
|
||||
}
|
||||
char *colon = strchr (line, ':');
|
||||
if (!colon) {
|
||||
continue;
|
||||
}
|
||||
*colon = 0;
|
||||
char *key = line;
|
||||
char *value = colon + 1;
|
||||
r_str_trim (key);
|
||||
r_str_trim (value);
|
||||
if (!strcmp (key, "Title")) {
|
||||
prompt->title = strdup (value);
|
||||
} else if (!strcmp (key, "Author")) {
|
||||
prompt->author = strdup (value);
|
||||
} else if (!strcmp (key, "Description")) {
|
||||
prompt->desc = strdup (value);
|
||||
} else if (!strcmp (key, "Command") || !strcmp (key, "Commands")) {
|
||||
prompt->command = strdup (value);
|
||||
} else if (!strcmp (key, "Prompt") || !strcmp (key, "Query")) {
|
||||
prompt->prompt = strdup (value);
|
||||
} else if (!strcmp (key, "Depends")) {
|
||||
prompt->
|
||||
requires
|
||||
= strdup (value);
|
||||
} else if (!strcmp (key, "If-Empty")) {
|
||||
prompt->if_empty = strdup (value);
|
||||
} else if (!strcmp (key, "If-Command")) {
|
||||
prompt->if_command = strdup (value);
|
||||
}
|
||||
}
|
||||
r_list_free (lines);
|
||||
R_LOG_ERROR ("Invalid prompt file format: %s", filepath);
|
||||
}
|
||||
|
||||
free (content);
|
||||
|
||||
+32
-2
@@ -451,6 +451,36 @@ static bool cb_r2ai_model(void *user, void *data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cb_r2ai_http_backend(void *user, void *data) {
|
||||
RCore *core = (RCore *)user;
|
||||
RConfigNode *node = (RConfigNode *)data;
|
||||
if (*node->value == '?') {
|
||||
r_cons_println (core->cons, "auto");
|
||||
r_cons_println (core->cons, "libcurl");
|
||||
r_cons_println (core->cons, "socket");
|
||||
r_cons_println (core->cons, "system");
|
||||
r_cons_println (core->cons, "curl");
|
||||
r_cons_println (core->cons, "pwsh");
|
||||
r_cons_println (core->cons, "powershell");
|
||||
r_cons_println (core->cons, "r2curl");
|
||||
return false;
|
||||
}
|
||||
// Validate the backend
|
||||
if (!strcmp (node->value, "auto") ||
|
||||
!strcmp (node->value, "libcurl") ||
|
||||
!strcmp (node->value, "socket") ||
|
||||
!strcmp (node->value, "system") ||
|
||||
!strcmp (node->value, "curl") ||
|
||||
!strcmp (node->value, "pwsh") ||
|
||||
!strcmp (node->value, "powershell") ||
|
||||
!strcmp (node->value, "r2curl")) {
|
||||
return true;
|
||||
}
|
||||
R_LOG_ERROR ("Invalid backend '%s'. Use '?' to list valid backends.", node->value);
|
||||
return false;
|
||||
// return false; // always return false to test
|
||||
}
|
||||
|
||||
R_IPI bool r2ai_init(RCorePluginSession *cps) {
|
||||
RCore *core = cps->core;
|
||||
// Initialize state
|
||||
@@ -476,6 +506,8 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
|
||||
r_config_desc (core->config, "r2ai.api", desc);
|
||||
free (desc);
|
||||
}
|
||||
r_config_set_cb (core->config, "r2ai.http.backend", "auto", &cb_r2ai_http_backend); // Options: auto, libcurl, socket, system, curl, pwsh, powershell, r2curl
|
||||
r_config_desc (core->config, "r2ai.http.backend", "HTTP backend to use (auto, libcurl, socket, system, curl, pwsh, powershell, r2curl)");
|
||||
r_config_set_cb (core->config, "r2ai.model", R2AI_DEFAULT_MODEL, &cb_r2ai_model);
|
||||
r_config_desc (core->config, "r2ai.model", "Model identifier for the selected provider (e.g. gpt-5-mini)");
|
||||
r_config_set (core->config, "r2ai.baseurl", "");
|
||||
@@ -534,8 +566,6 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
|
||||
r_config_desc (core->config, "r2ai.http.max_retries", "Maximum number of HTTP retries for failed requests");
|
||||
r_config_set_i (core->config, "r2ai.http.max_backoff", 30);
|
||||
r_config_desc (core->config, "r2ai.http.max_backoff", "Maximum backoff time (seconds) between HTTP retries");
|
||||
r_config_set (core->config, "r2ai.http.backend", "auto"); // Options: auto, libcurl, socket, system
|
||||
r_config_desc (core->config, "r2ai.http.backend", "HTTP backend to use (auto, libcurl, socket, system)");
|
||||
r_config_set_b (core->config, "r2ai.http.use_files", false);
|
||||
r_config_desc (core->config, "r2ai.http.use_files", "Use temporary files to pass HTTP request/response payloads (true/false)");
|
||||
r_config_set_b (core->config, "r2ai.auto.raw", false);
|
||||
|
||||
+23
-1
@@ -11,7 +11,7 @@
|
||||
#include "r_vdb.h"
|
||||
#include "markdown.h"
|
||||
|
||||
#define R2AI_VERSION "1.2.2"
|
||||
#define R2AI_VERSION "1.2.4"
|
||||
|
||||
#if 0
|
||||
#define R2AI_DEFAULT_MODEL "gpt-5-mini"
|
||||
@@ -25,6 +25,28 @@
|
||||
|
||||
#define R2AI_DEFAULT_VECTORS 16
|
||||
|
||||
// HTTP configuration structure
|
||||
typedef struct {
|
||||
int timeout;
|
||||
int max_retries;
|
||||
int max_backoff;
|
||||
} R2AI_HttpConfig;
|
||||
|
||||
// HTTP request structure
|
||||
typedef struct {
|
||||
R2AI_HttpConfig config;
|
||||
const char *url;
|
||||
const char *data;
|
||||
const char * const *headers;
|
||||
} HTTPRequest;
|
||||
|
||||
// HTTP response structure
|
||||
typedef struct {
|
||||
char *body;
|
||||
int code;
|
||||
int length;
|
||||
} HttpResponse;
|
||||
|
||||
#if R2_VERSION_NUMBER < 60000
|
||||
#error Your radare2 is too old
|
||||
#endif
|
||||
|
||||
-1134
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user