mirror of
https://github.com/radareorg/r2shell.git
synced 2026-11-09 12:41:26 -10:00
599 lines
12 KiB
C
599 lines
12 KiB
C
/* radare2 - LGPL - Copyright 2020 - pancake */
|
|
|
|
#include <r_core.h>
|
|
#include <r_util.h>
|
|
#include "r_shell.h"
|
|
|
|
#define handle_skip(s, in) \
|
|
if (*in == '\\') { escape = true;\
|
|
} else if (*in == skip && !escape) { skip = 0;\
|
|
} else { escape = false; }
|
|
|
|
R_API RShell *r_shell_new(void) {
|
|
RShell *s = R_NEW0 (RShell);
|
|
s->cmds = ht_pp_new0 ();
|
|
return s;
|
|
}
|
|
|
|
R_API void r_shell_free(RShell *s) {
|
|
r_list_free (s->stack);
|
|
free (s->error);
|
|
free (s);
|
|
}
|
|
|
|
R_API void r_shell_instruction_free(RShellInstruction *si) {
|
|
r_list_free (si->args);
|
|
r_list_free (si->ats);
|
|
r_list_free (si->fors);
|
|
r_list_free (si->trifors);
|
|
free (si->_argstr);
|
|
free (si);
|
|
}
|
|
|
|
R_API RShellCommand *r_shell_command_new(const char *ocmd) {
|
|
if (R_STR_ISEMPTY (ocmd)) {
|
|
return NULL;
|
|
}
|
|
RShellCommand *sc = R_NEW0 (RShellCommand);
|
|
sc->cmd = r_str_trim_dup (ocmd);
|
|
return sc;
|
|
}
|
|
|
|
R_API void r_shell_command_free(RShellCommand *s) {
|
|
r_free (s->cmd);
|
|
r_free (s->comment);
|
|
}
|
|
|
|
R_API void r_shell_register(RShell *s, RShellHandler *sh) {
|
|
ht_pp_insert (s->cmds, sh->cmd, sh);
|
|
}
|
|
|
|
static RList *shell_parse_split(RShell *s, const char *_cmd) {
|
|
size_t nest = 0;
|
|
const char *comment = NULL;
|
|
char *pcmd = strdup (_cmd);
|
|
char *cmd = pcmd;
|
|
RList *stack = r_list_newf ((RListFree)r_shell_command_free);
|
|
char *ocmd = cmd;
|
|
int skip = 0;
|
|
bool escape = false;
|
|
while (*cmd) {
|
|
if (skip) {
|
|
if (nest) {
|
|
if (!strncmp (cmd, "$(", 2)) {
|
|
nest++;
|
|
cmd++;
|
|
} else if (*cmd == ')') {
|
|
skip = 0;
|
|
}
|
|
}
|
|
handle_skip (s, cmd);
|
|
cmd++;
|
|
continue;
|
|
}
|
|
escape = false;
|
|
switch (*cmd) {
|
|
case '$':
|
|
if (cmd[1] == '(') {
|
|
nest++;
|
|
skip = true;
|
|
// walk until ')' and increase nesting if '$(' is found again
|
|
}
|
|
break;
|
|
case '\\':
|
|
escape = true;
|
|
break;
|
|
case '#':
|
|
// comments ends
|
|
*cmd = 0;
|
|
comment = r_str_trim_head_ro (cmd + 1);
|
|
goto beach;
|
|
case '"':
|
|
case '\'':
|
|
case '`':
|
|
skip = *cmd;
|
|
break;
|
|
case '\n':
|
|
case ';':
|
|
if (!skip) {
|
|
*cmd = 0;
|
|
RShellCommand *c = r_shell_command_new (ocmd);
|
|
if (c) {
|
|
r_list_append (stack, c);
|
|
}
|
|
ocmd = cmd + 1;
|
|
}
|
|
break;
|
|
}
|
|
cmd++;
|
|
}
|
|
beach:
|
|
if (skip) {
|
|
free (s->error);
|
|
s->error = r_str_newf ("Missing invalid command%c", 10);
|
|
}
|
|
RShellCommand *c = r_shell_command_new (ocmd);
|
|
if (c) {
|
|
if (comment) {
|
|
c->comment = strdup (comment);
|
|
}
|
|
r_list_append (stack, c);
|
|
}
|
|
free (pcmd);
|
|
return stack;
|
|
}
|
|
|
|
static void shell_split_args(RShellInstruction *si, const char *in) {
|
|
const char *oin = in;
|
|
bool escape = false;
|
|
const char *ats = NULL;
|
|
RList *args = si->args;
|
|
int skip = 0;
|
|
int nest = 0;
|
|
while (*in) {
|
|
if (skip) {
|
|
if (nest) {
|
|
if (!strncmp (in, "$(", 2)) {
|
|
nest++;
|
|
} else if (*in == ')') {
|
|
nest--;
|
|
if (nest < 1) {
|
|
skip = false;
|
|
}
|
|
}
|
|
} else {
|
|
handle_skip (s, in);
|
|
}
|
|
in++;
|
|
continue;
|
|
}
|
|
switch (*in) {
|
|
case '@': {
|
|
char *a = r_str_ndup (oin, in - oin);
|
|
r_str_trim (a);
|
|
if (!R_STR_ISEMPTY (a)) {
|
|
r_list_append (args, a);
|
|
} else {
|
|
free (a);
|
|
}
|
|
if (in[1] == '@') { // @@
|
|
if (in[2] == '@') { // @@@
|
|
args = si->trifors;
|
|
in += 2;
|
|
oin = in + 1;
|
|
} else {
|
|
args = si->fors;
|
|
in += 1;
|
|
oin = in + 1;
|
|
}
|
|
} else { // @
|
|
args = si->ats;
|
|
oin = in + 1;
|
|
}
|
|
}
|
|
break;
|
|
case '$':
|
|
if (in[1] == '(') {
|
|
nest++;
|
|
skip = ')';
|
|
}
|
|
break;
|
|
case '`':
|
|
case '\'':
|
|
case '"':
|
|
skip = *in;
|
|
break;
|
|
case '>':
|
|
si->dumpstr = r_str_trim_dup (in + 1);
|
|
goto done;
|
|
case '|':
|
|
si->pipestr = r_str_trim_dup (in + 1);
|
|
goto done;
|
|
case ' ':
|
|
if (si->args == args) {
|
|
char *a = r_str_ndup (oin, in - oin);
|
|
r_str_trim (a);
|
|
if (!R_STR_ISEMPTY (a)) {
|
|
r_list_append (args, a);
|
|
} else {
|
|
free (a);
|
|
}
|
|
oin = in + 1;
|
|
}
|
|
break;
|
|
}
|
|
in++;
|
|
}
|
|
done:
|
|
if (*oin) {
|
|
char *a = r_str_ndup (oin, in - oin);
|
|
if (!R_STR_ISEMPTY (a)) {
|
|
r_str_trim (a);
|
|
r_list_append (args, a);
|
|
} else {
|
|
free (a);
|
|
}
|
|
}
|
|
}
|
|
|
|
R_API RShellInstruction *r_shell_decode(RShell *s, RShellCommand *sc) {
|
|
RShellInstruction *si = R_NEW0 (RShellInstruction);
|
|
si->sc = sc;
|
|
// skip initial digits and set
|
|
const char *p = sc->cmd;
|
|
while (isdigit (*p)) {
|
|
p++;
|
|
}
|
|
si->repeat = atoi (sc->cmd);
|
|
if (si->repeat < 0) {
|
|
si->repeat = 0;
|
|
}
|
|
si->_argstr = strdup (p);
|
|
char *q = strdup (p);
|
|
free (sc->cmd);
|
|
sc->cmd = q;
|
|
si->args = r_list_newf (free);
|
|
si->ats = r_list_newf (free);
|
|
si->fors = r_list_newf (free);
|
|
si->trifors = r_list_newf (free);
|
|
shell_split_args (si, si->_argstr);
|
|
return si;
|
|
}
|
|
|
|
R_API RShellHandler *r_shell_find_handler(RShell *s, const char *cmd) {
|
|
RShellHandler *sh = NULL;
|
|
char *c = strdup (cmd);
|
|
while (*c) {
|
|
sh = ht_pp_find (s->cmds, c, NULL);
|
|
if (sh) {
|
|
break;
|
|
}
|
|
c[strlen (c) - 1] = 0;
|
|
}
|
|
free (c);
|
|
return sh;
|
|
}
|
|
|
|
R_API void r_shell_result_free (RShellResult *sr) {
|
|
free (sr->output);
|
|
free (sr->error);
|
|
free (sr);
|
|
}
|
|
|
|
R_API RShellResult* r_shell_result_new(char *output, char *error, int rc, RShellUndo *undo) {
|
|
RShellResult *r = R_NEW (RShellResult);
|
|
r->output = output;
|
|
r->error = error;
|
|
r->rc = rc;
|
|
r->undo = undo;
|
|
return r;
|
|
}
|
|
|
|
R_API RShellResult *r_shell_execute(RShell *s, RShellInstruction *si) {
|
|
const char *cmd = r_list_get_n (si->args, 0);
|
|
if (cmd) {
|
|
RShellHandler *sh = r_shell_find_handler (s, cmd);
|
|
if (sh) {
|
|
eprintf ("FIND %p (%s)%c", sh, sh->cmd, 10);
|
|
if (sh && sh->cb) {
|
|
RShellResult *res = sh->cb (s, si);
|
|
if (res && res->undo) {
|
|
r_list_push (sh->undo, res->undo);
|
|
while (sh->undo->length > sh->max_undo) {
|
|
RShellUndo *u = r_list_pop_head (sh->undo);
|
|
if (u->free) {
|
|
u->free (u->user);
|
|
} else {
|
|
free (u->user);
|
|
}
|
|
free (u);
|
|
}
|
|
}
|
|
return sh->cb (s, si);
|
|
}
|
|
return r_shell_result_new (NULL, strdup ("Missing callback in handler."), 1, NULL);
|
|
}
|
|
}
|
|
return r_shell_result_new (NULL, strdup ("Invalid command."), 1, NULL);
|
|
}
|
|
|
|
R_API RShellCommand *r_shell_fetch(RShell *s, const char *cmd) {
|
|
if (cmd) {
|
|
if (s->stack) {
|
|
if (r_list_length (s->stack) > 0) {
|
|
eprintf ("Warning: Pending commands lost.%c", 10);
|
|
}
|
|
r_list_free (s->stack);
|
|
}
|
|
s->stack = shell_parse_split (s, cmd);
|
|
}
|
|
return r_list_pop_head (s->stack);
|
|
}
|
|
|
|
static bool r_shell_is_suffix(char ch) {
|
|
switch (ch) {
|
|
case 'j': // json
|
|
case 'q': // quiet
|
|
case '*': // r2
|
|
case ',': // table
|
|
return ch;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char *find_expr(const char *a, const char **eof) {
|
|
int nest = 0;
|
|
const char *o = a;
|
|
const char *p = a;
|
|
while (*a) {
|
|
if (!strncmp (a, "$(", 2)) {
|
|
p = a + 2;
|
|
nest++;
|
|
} else if (*a == ')') {
|
|
*eof = a;
|
|
return p;
|
|
}
|
|
a++;
|
|
}
|
|
if (nest > 0) {
|
|
eprintf ("Invalid nest.%c", 10);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void shell_eval(RShell *s, RListIter *iter, const char *arg) {
|
|
const char *a, *b;
|
|
if (*arg == '"') {
|
|
char *r = r_str_ndup (arg + 1, strlen (arg + 1) - 1);
|
|
r_str_unescape (r);
|
|
free (iter->data);
|
|
iter->data = (void *)r;
|
|
return;
|
|
}
|
|
loop:
|
|
for (a = arg; *a ; a++) {
|
|
if (!strncmp (a, "$(", 2)) {
|
|
const char *eof, *begin = find_expr (a, &eof);
|
|
char *piece_a = r_str_ndup (arg, begin - arg - 2);
|
|
char *piece_b = r_str_ndup (begin, eof - begin);
|
|
char *piece_c = strdup (eof + 1);
|
|
if (eof) {
|
|
char *out = r_shell_fdex (s, piece_b);
|
|
char *res = r_str_newf ("%s%s%s", piece_a, out, piece_c);
|
|
free (iter->data);
|
|
arg = res;
|
|
iter->data = (void *)arg;
|
|
free (out);
|
|
}
|
|
free (piece_a);
|
|
free (piece_b);
|
|
free (piece_c);
|
|
goto loop;
|
|
}
|
|
if (*a == '`') {
|
|
for (b = ++a; *b; b++) {
|
|
if (*b == '`') {
|
|
char *piece_a = r_str_ndup (arg, a - arg - 1);
|
|
char *piece_b = r_str_ndup (a, b - a);
|
|
char *piece_c = strdup (b + 1);
|
|
char *out = r_shell_fdex (s, piece_b);
|
|
char *res = r_str_newf ("%s%s%s", piece_a, out, piece_c);
|
|
free (iter->data);
|
|
arg = res;
|
|
iter->data = (void *)arg;
|
|
free (out);
|
|
free (piece_a);
|
|
free (piece_b);
|
|
free (piece_c);
|
|
goto loop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (*a == '`') {
|
|
eprintf ("Missing closing.%c", 10);
|
|
}
|
|
}
|
|
|
|
static void instruction_suffix(RShellInstruction *si, const char *arg) {
|
|
size_t arg_len = strlen (arg);
|
|
if (arg_len > 1) {
|
|
char ch = arg[arg_len - 1];
|
|
if (r_shell_is_suffix (ch)) {
|
|
si->suffix = ch;
|
|
}
|
|
}
|
|
}
|
|
|
|
R_API bool r_shell_eval(RShell *s, RShellInstruction *si);
|
|
|
|
R_API char *r_shell_fdex(RShell *s, const char *cmd) {
|
|
RShellCommand *c = r_shell_fetch (s, cmd);
|
|
if (c) {
|
|
RShellInstruction *i = r_shell_decode (s, c);
|
|
if (i) {
|
|
r_shell_eval (s, i);
|
|
RShellResult *sr = r_shell_execute (s, i);
|
|
if (sr) {
|
|
char *output = NULL;
|
|
if (sr->output) {
|
|
output = sr->output;
|
|
sr->output = NULL;
|
|
}
|
|
if (sr->error) {
|
|
eprintf ("%s\n", sr->error);
|
|
}
|
|
r_shell_result_free (sr);
|
|
return output;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
R_API bool r_shell_eval(RShell *s, RShellInstruction *si) {
|
|
RListIter *iter;
|
|
const char *arg;
|
|
// evaluate all
|
|
r_list_foreach (si->args, iter, arg) {
|
|
shell_eval (s, iter, arg);
|
|
}
|
|
char *arg0 = r_list_get_n (si->args, 0);
|
|
if (!R_STR_ISEMPTY (arg0)) {
|
|
instruction_suffix (si, arg0);
|
|
}
|
|
r_list_foreach (si->ats, iter, arg) {
|
|
shell_eval (s, iter, arg);
|
|
}
|
|
r_list_foreach (si->fors, iter, arg) {
|
|
shell_eval (s, iter, arg);
|
|
}
|
|
r_list_foreach (si->trifors, iter, arg) {
|
|
shell_eval (s, iter, arg);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
R_API void r_shell_json_from_instruction(PJ *pj, RShellInstruction *si) {
|
|
RListIter *iter;
|
|
char *arg;
|
|
if (si->atstr) {
|
|
pj_ks (pj, "atstr", si->atstr);
|
|
}
|
|
if (si->pipestr) {
|
|
pj_ks (pj, "pipestr", si->pipestr);
|
|
}
|
|
if (si->dumpstr) {
|
|
pj_ks (pj, "dumpstr", si->dumpstr);
|
|
}
|
|
pj_kn (pj, "repeat", si->repeat);
|
|
pj_ka (pj, "args");
|
|
r_list_foreach (si->args, iter, arg) {
|
|
pj_s (pj, arg);
|
|
}
|
|
pj_end (pj);
|
|
if (!r_list_empty (si->ats)) {
|
|
pj_ka (pj, "ats");
|
|
r_list_foreach (si->ats, iter, arg) {
|
|
pj_s (pj, arg);
|
|
}
|
|
pj_end (pj);
|
|
}
|
|
if (!r_list_empty (si->fors)) {
|
|
pj_ka (pj, "fors");
|
|
r_list_foreach (si->fors, iter, arg) {
|
|
pj_s (pj, arg);
|
|
}
|
|
pj_end (pj);
|
|
}
|
|
if (!r_list_empty (si->trifors)) {
|
|
pj_ka (pj, "trifors");
|
|
r_list_foreach (si->trifors, iter, arg) {
|
|
pj_s (pj, arg);
|
|
}
|
|
pj_end (pj);
|
|
}
|
|
}
|
|
|
|
static void run_command(RCore *core, RShell *s, const char *cmd) {
|
|
// TODO: configure shell with proper syntax for command parsing
|
|
RShellCommand *sc = r_shell_fetch (s, cmd);
|
|
|
|
PJ *pj = s->pj = pj_new ();;
|
|
pj_a (pj);
|
|
while (sc) {
|
|
RShellInstruction *si = r_shell_decode (s, sc);
|
|
if (!si) {
|
|
break;
|
|
}
|
|
pj_o (pj);
|
|
pj_ks (pj, "command", sc->cmd);
|
|
if (sc->comment) {
|
|
pj_ks (pj, "comment", sc->comment);
|
|
}
|
|
r_shell_eval (s, si);
|
|
r_shell_json_from_instruction (pj, si);
|
|
RListIter *iter;
|
|
char *arg;
|
|
RShellResult *sr = r_shell_execute (s, si);
|
|
pj_ka (pj, "result");
|
|
if (sr->rc) {
|
|
pj_kd (s->pj, "rc", sr->rc);
|
|
}
|
|
if (sr->error) {
|
|
pj_ks (s->pj, "error", sr->output);
|
|
}
|
|
if (sr->output) {
|
|
pj_ks (s->pj, "output", sr->output);
|
|
}
|
|
r_shell_result_free (sr);
|
|
pj_end (s->pj);
|
|
r_shell_instruction_free (si);
|
|
pj_end (s->pj);
|
|
// fetch the next command (separated by newlines or semicolons)
|
|
sc = r_shell_fetch (s, NULL);
|
|
}
|
|
pj_end (s->pj);
|
|
if (s->error) {
|
|
eprintf ("Error: %s%c", s->error, 10);
|
|
R_FREE (s->error);
|
|
}
|
|
{
|
|
char *json = pj_drain (s->pj);
|
|
char *ij = r_print_json_indent (json, true, " ", NULL);
|
|
printf ("%s%c", ij, 10);
|
|
free (ij);
|
|
free (json);
|
|
}
|
|
}
|
|
|
|
R_API RShellHandler *r_shell_handler_new(const char *cmd, RShellCallback cb) {
|
|
RShellHandler *sh = R_NEW0 (RShellHandler);
|
|
sh->cmd = strdup (cmd);
|
|
sh->cb = cb;
|
|
return sh;
|
|
}
|
|
|
|
R_API bool r_shell_undo(RShell *sh) {
|
|
if (!sh || !sh->undo || !sh->undo->length) {
|
|
return true;
|
|
}
|
|
RShellUndo *u = r_list_pop (sh->undo);
|
|
const bool ret = u->cb (u->user);
|
|
if (u->free) {
|
|
u->free (u->user);
|
|
} else {
|
|
free (u->user);
|
|
}
|
|
free (u);
|
|
return ret;
|
|
}
|
|
|
|
static RShellResult *cmd_px(RShell *s, RShellInstruction *si) {
|
|
return r_shell_result_new (strdup ("hexdump"), NULL, 0, NULL);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
RShell *s = r_shell_new ();
|
|
|
|
r_shell_register (s, r_shell_handler_new ("px", &cmd_px));
|
|
|
|
RCore *core = r_core_new ();
|
|
if (argc > 1) {
|
|
run_command (core, s, argv[1]);
|
|
} else {
|
|
// run_command (core, s, "3s+32;px 64;w \"he#lo; wo#ld\"");
|
|
for (;;) {
|
|
const char *line = r_line_readline ();
|
|
if (!line || *line == 'q') {
|
|
break;
|
|
}
|
|
run_command (core, s, line);
|
|
}
|
|
}
|
|
r_core_free (core);
|
|
r_shell_free (s);
|
|
}
|
|
|