Skip to content

Commit d4a9474

Browse files
jonasschnellirandom-zebra
authored andcommitted
[Qt] RPC-Console: support nested commands and simple value queries
Commands can be executed with bracket syntax, example: `getwalletinfo()`. Commands can be nested, example: `sendtoaddress(getnewaddress(), 10)`. Simple queries are possible: `listunspent()[0][txid]` Object values are accessed with a non-quoted string, example: [txid]. Fully backward compatible. `generate 101` is identical to `generate(101)` Result value queries indicated with `[]` require the new brackets syntax. Comma as argument separator is now also possible: `sendtoaddress,<address>,<amount>` Space as argument separator works also with the bracket syntax, example: `sendtoaddress(getnewaddress() 10) No dept limitation, complex commands are possible: `decoderawtransaction(getrawtransaction(getblock(getbestblockhash())[tx][0]))[vout][0][value]`
1 parent e2085e5 commit d4a9474

File tree

4 files changed

+338
-184
lines changed

4 files changed

+338
-184
lines changed

src/qt/pivx/settings/settingsconsolewidget.cpp

Lines changed: 164 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -100,97 +100,191 @@ class QtRPCTimerInterface: public RPCTimerInterface
100100
#include "qt/pivx/settings/moc_settingsconsolewidget.cpp"
101101

102102
/**
103-
* Split shell command line into a list of arguments. Aims to emulate \c bash and friends.
103+
* Split shell command line into a list of arguments and execute the command(s).
104+
* Aims to emulate \c bash and friends.
104105
*
105-
* - Arguments are delimited with whitespace
106+
* - Command nesting is possible with brackets [example: validateaddress(getnewaddress())]
107+
* - Arguments are delimited with whitespace or comma
106108
* - Extra whitespace at the beginning and end and between arguments will be ignored
107109
* - Text can be "double" or 'single' quoted
108110
* - The backslash \c \ is used as escape character
109111
* - Outside quotes, any character can be escaped
110112
* - Within double quotes, only escape \c " and backslashes before a \c " or another backslash
111113
* - Within single quotes, no escaping is possible and no special interpretation takes place
112114
*
113-
* @param[out] args Parsed arguments will be appended to this list
115+
* @param[out] result stringified Result from the executed command(chain)
114116
* @param[in] strCommand Command line to split
115117
*/
116-
bool parseCommandLineSettings(std::vector<std::string>& args, const std::string& strCommand)
118+
bool RPCExecuteCommandLine(std::string &strResult, const std::string &strCommand)
117119
{
118-
enum CmdParseState {
120+
std::vector< std::vector<std::string> > stack;
121+
stack.push_back(std::vector<std::string>());
122+
123+
enum CmdParseState
124+
{
119125
STATE_EATING_SPACES,
120126
STATE_ARGUMENT,
121127
STATE_SINGLEQUOTED,
122128
STATE_DOUBLEQUOTED,
123129
STATE_ESCAPE_OUTER,
124-
STATE_ESCAPE_DOUBLEQUOTED
130+
STATE_ESCAPE_DOUBLEQUOTED,
131+
STATE_COMMAND_EXECUTED,
132+
STATE_COMMAND_EXECUTED_INNER
125133
} state = STATE_EATING_SPACES;
126134
std::string curarg;
127-
for (char ch : strCommand) {
128-
switch (state) {
129-
case STATE_ARGUMENT: // In or after argument
130-
case STATE_EATING_SPACES: // Handle runs of whitespace
131-
switch (ch) {
132-
case '"':
133-
state = STATE_DOUBLEQUOTED;
134-
break;
135-
case '\'':
136-
state = STATE_SINGLEQUOTED;
137-
break;
138-
case '\\':
139-
state = STATE_ESCAPE_OUTER;
140-
break;
141-
case ' ':
142-
case '\n':
143-
case '\t':
144-
if (state == STATE_ARGUMENT) // Space ends argument
135+
UniValue lastResult;
136+
137+
std::string strCommandTerminated = strCommand;
138+
if (strCommandTerminated.back() != '\n')
139+
strCommandTerminated += "\n";
140+
for(char ch: strCommandTerminated)
141+
{
142+
switch(state)
143+
{
144+
case STATE_COMMAND_EXECUTED_INNER:
145+
case STATE_COMMAND_EXECUTED:
146+
{
147+
bool breakParsing = true;
148+
switch(ch)
149+
{
150+
case '[': curarg.clear(); state = STATE_COMMAND_EXECUTED_INNER; break;
151+
default:
152+
if (state == STATE_COMMAND_EXECUTED_INNER)
153+
{
154+
if (ch != ']')
155+
{
156+
// append char to the current argument (which is also used for the query command)
157+
curarg += ch;
158+
break;
159+
}
160+
if (curarg.size())
161+
{
162+
// if we have a value query, query arrays with index and objects with a string key
163+
UniValue subelement;
164+
if (lastResult.isArray())
165+
{
166+
for(char argch: curarg)
167+
if (!std::isdigit(argch))
168+
throw std::runtime_error("Invalid result query");
169+
subelement = lastResult[atoi(curarg.c_str())];
170+
}
171+
else if (lastResult.isObject())
172+
subelement = find_value(lastResult, curarg);
173+
else
174+
throw std::runtime_error("Invalid result query"); //no array or object: abort
175+
lastResult = subelement;
176+
}
177+
178+
state = STATE_COMMAND_EXECUTED;
179+
break;
180+
}
181+
// don't break parsing when the char is required for the next argument
182+
breakParsing = false;
183+
184+
// pop the stack and return the result to the current command arguments
185+
stack.pop_back();
186+
187+
// don't stringify the json in case of a string to avoid doublequotes
188+
if (lastResult.isStr())
189+
curarg = lastResult.get_str();
190+
else
191+
curarg = lastResult.write(2);
192+
193+
// if we have a non empty result, use it as stack argument otherwise as general result
194+
if (curarg.size())
145195
{
146-
args.push_back(curarg);
147-
curarg.clear();
196+
if (stack.size())
197+
stack.back().push_back(curarg);
198+
else
199+
strResult = curarg;
148200
}
201+
curarg.clear();
202+
// assume eating space state
149203
state = STATE_EATING_SPACES;
150-
break;
151-
default:
152-
curarg += ch;
153-
state = STATE_ARGUMENT;
154204
}
205+
if (breakParsing)
206+
break;
207+
}
208+
case STATE_ARGUMENT: // In or after argument
209+
case STATE_EATING_SPACES: // Handle runs of whitespace
210+
switch(ch)
211+
{
212+
case '"': state = STATE_DOUBLEQUOTED; break;
213+
case '\'': state = STATE_SINGLEQUOTED; break;
214+
case '\\': state = STATE_ESCAPE_OUTER; break;
215+
case '(': case ')': case '\n':
216+
if (state == STATE_ARGUMENT)
217+
{
218+
if (ch == '(' && stack.size() && stack.back().size() > 0)
219+
stack.push_back(std::vector<std::string>());
220+
if (curarg.size())
221+
{
222+
// don't allow commands after executed commands on baselevel
223+
if (!stack.size())
224+
throw std::runtime_error("Invalid Syntax");
225+
stack.back().push_back(curarg);
226+
}
227+
curarg.clear();
228+
state = STATE_EATING_SPACES;
229+
}
230+
if ((ch == ')' || ch == '\n') && stack.size() > 0)
231+
{
232+
std::string strPrint;
233+
// Convert argument list to JSON objects in method-dependent way,
234+
// and pass it along with the method name to the dispatcher.
235+
JSONRPCRequest req;
236+
req.params = RPCConvertValues(stack.back()[0], std::vector<std::string>(stack.back().begin() + 1, stack.back().end()));
237+
req.strMethod = stack.back()[0];
238+
lastResult = tableRPC.execute(req);
239+
state = STATE_COMMAND_EXECUTED;
240+
curarg.clear();
241+
}
242+
break;
243+
case ' ': case ',': case '\t':
244+
if(state == STATE_ARGUMENT) // Space ends argument
245+
{
246+
if (curarg.size())
247+
stack.back().push_back(curarg);
248+
curarg.clear();
249+
}
250+
state = STATE_EATING_SPACES;
251+
break;
252+
default: curarg += ch; state = STATE_ARGUMENT;
253+
}
155254
break;
156255
case STATE_SINGLEQUOTED: // Single-quoted string
157-
switch (ch) {
158-
case '\'':
159-
state = STATE_ARGUMENT;
160-
break;
161-
default:
162-
curarg += ch;
163-
}
256+
switch(ch)
257+
{
258+
case '\'': state = STATE_ARGUMENT; break;
259+
default: curarg += ch;
260+
}
164261
break;
165262
case STATE_DOUBLEQUOTED: // Double-quoted string
166-
switch (ch) {
167-
case '"':
168-
state = STATE_ARGUMENT;
169-
break;
170-
case '\\':
171-
state = STATE_ESCAPE_DOUBLEQUOTED;
172-
break;
173-
default:
174-
curarg += ch;
175-
}
263+
switch(ch)
264+
{
265+
case '"': state = STATE_ARGUMENT; break;
266+
case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break;
267+
default: curarg += ch;
268+
}
176269
break;
177270
case STATE_ESCAPE_OUTER: // '\' outside quotes
178-
curarg += ch;
179-
state = STATE_ARGUMENT;
271+
curarg += ch; state = STATE_ARGUMENT;
180272
break;
181-
case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
182-
if (ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
183-
curarg += ch;
184-
state = STATE_DOUBLEQUOTED;
273+
case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
274+
if(ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
275+
curarg += ch; state = STATE_DOUBLEQUOTED;
185276
break;
186277
}
187278
}
188279
switch (state) // final state
189280
{
190-
case STATE_EATING_SPACES:
191-
return true;
281+
case STATE_COMMAND_EXECUTED:
282+
if (lastResult.isStr())
283+
strResult = lastResult.get_str();
284+
else
285+
strResult = lastResult.write(2);
192286
case STATE_ARGUMENT:
193-
args.push_back(curarg);
287+
case STATE_EATING_SPACES:
194288
return true;
195289
default: // ERROR to end in one of the other states
196290
return false;
@@ -199,43 +293,26 @@ bool parseCommandLineSettings(std::vector<std::string>& args, const std::string&
199293

200294
void RPCExecutor::requestCommand(const QString& command)
201295
{
202-
std::vector<std::string> args;
203-
if (!parseCommandLineSettings(args, command.toStdString())) {
204-
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
205-
return;
206-
}
207-
if (args.empty())
208-
return; // Nothing to do
209296
try {
210-
std::string strPrint;
211-
// Convert argument list to JSON objects in method-dependent way,
212-
// and pass it along with the method name to the dispatcher.
213-
JSONRPCRequest req;
214-
req.params = RPCConvertValues(args[0], std::vector<std::string>(args.begin() + 1, args.end()));
215-
req.strMethod = args[0];
216-
UniValue result = tableRPC.execute(req);
217-
218-
// Format result reply
219-
if (result.isNull())
220-
strPrint = "";
221-
else if (result.isStr())
222-
strPrint = result.get_str();
223-
else
224-
strPrint = result.write(2);
225-
226-
Q_EMIT reply(SettingsConsoleWidget::CMD_REPLY, QString::fromStdString(strPrint));
297+
std::string result;
298+
std::string executableCommand = command.toStdString() + "\n";
299+
if (!RPCExecuteCommandLine(result, executableCommand)) {
300+
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
301+
return;
302+
}
303+
Q_EMIT reply(SettingsConsoleWidget::CMD_REPLY, QString::fromStdString(result));
227304
} catch (UniValue& objError) {
228305
try // Nice formatting for standard-format error
229306
{
230307
int code = find_value(objError, "code").get_int();
231308
std::string message = find_value(objError, "message").get_str();
232-
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
233-
} catch (std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message
309+
Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
310+
} catch (const std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message
234311
{ // Show raw JSON object
235-
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString::fromStdString(objError.write()));
312+
Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write()));
236313
}
237-
} catch (std::exception& e) {
238-
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));
314+
} catch (const std::exception& e) {
315+
Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));
239316
}
240317
}
241318

@@ -457,18 +534,11 @@ static bool PotentiallyDangerousCommand(const QString& cmd)
457534
return true;
458535
}
459536
if (cmd.size() >= 13 && cmd.leftRef(11) == "dumpprivkey") {
460-
// valid PIVX Transparent Address
461-
std::vector<std::string> args;
462-
parseCommandLineSettings(args, cmd.toStdString());
463-
return (args.size() == 2 && IsValidDestinationString(args[1], false));
537+
return true;
464538
}
465539
if (cmd.size() >= 18 && cmd.leftRef(16) == "exportsaplingkey") {
466-
// valid PIVX Shield Address
467-
std::vector<std::string> args;
468-
parseCommandLineSettings(args, cmd.toStdString());
469-
return (args.size() == 2 && KeyIO::IsValidPaymentAddressString(args[1]));
540+
return true;
470541
}
471-
472542
return false;
473543
}
474544

0 commit comments

Comments
 (0)