Browse Source

Merge pull request #1758 from laanwj/2012_08_uiconsole_parsing

Fix RPC console parser to handle escaped arguments more like bash
0.8
Gregory Maxwell 12 years ago
parent
commit
ddbddcb31e
  1. 131
      src/qt/rpcconsole.cpp

131
src/qt/rpcconsole.cpp

@ -13,7 +13,6 @@
#include <QUrl> #include <QUrl>
#include <QScrollBar> #include <QScrollBar>
#include <boost/tokenizer.hpp>
#include <openssl/crypto.h> #include <openssl/crypto.h>
// TODO: make it possible to filter out categories (esp debug messages when implemented) // TODO: make it possible to filter out categories (esp debug messages when implemented)
@ -54,34 +53,114 @@ void RPCExecutor::start()
// Nothing to do // Nothing to do
} }
void RPCExecutor::request(const QString &command) /**
* Split shell command line into a list of arguments. Aims to emulate \c bash and friends.
*
* - Arguments are delimited with whitespace
* - Extra whitespace at the beginning and end and between arguments will be ignored
* - Arguments can be "double" or 'single' quoted. Those are treated the same.
* - The backslash '\' is used as escape character
* - Outside quotes, any character can be escaped
* - Within double quotes, only escape double quotes with \" and backslashes with \\
* - Within single quotes, only escape single quotes with \' and backslashes with \\
*
* @param[out] args Parsed arguments will be appended to this list
* @param[in] strCommand Command line to split
*/
bool parseCommandLine(std::vector<std::string> &args, const std::string &strCommand)
{ {
// Parse shell-like command line into separate arguments enum CmdParseState
std::string strMethod;
std::vector<std::string> strParams;
try {
boost::escaped_list_separator<char> els('\\',' ','\"');
std::string strCommand = command.toStdString();
boost::tokenizer<boost::escaped_list_separator<char> > tok(strCommand, els);
int n = 0;
for(boost::tokenizer<boost::escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end();++beg,++n)
{ {
if(n == 0) // First parameter is the command STATE_EATING_SPACES,
strMethod = *beg; STATE_ARGUMENT,
else STATE_SINGLEQUOTED,
strParams.push_back(*beg); STATE_DOUBLEQUOTED,
STATE_ESCAPE_OUTER,
STATE_ESCAPE_SINGLEQUOTED,
STATE_ESCAPE_DOUBLEQUOTED
} state = STATE_EATING_SPACES;
std::string curarg;
foreach(char ch, strCommand)
{
switch(state)
{
case STATE_ARGUMENT: // After argument
case STATE_EATING_SPACES: // Handle runs of spaces
switch(ch)
{
case '"': state = STATE_DOUBLEQUOTED; break;
case '\'': state = STATE_SINGLEQUOTED; break;
case '\\': state = STATE_ESCAPE_OUTER; break;
case ' ': case '\n': case '\t':
if(state == STATE_ARGUMENT) // Space ends argument
{
args.push_back(curarg);
curarg.clear();
} }
state = STATE_EATING_SPACES;
break;
default: curarg += ch; state = STATE_ARGUMENT;
} }
catch(boost::escaped_list_error &e) break;
case STATE_SINGLEQUOTED: // Single-quoted string
switch(ch)
{ {
emit reply(RPCConsole::CMD_ERROR, QString("Parse error")); case '\'': state = STATE_ARGUMENT; break;
return; case '\\': state = STATE_ESCAPE_SINGLEQUOTED; break;
default: curarg += ch;
}
break;
case STATE_DOUBLEQUOTED: // Double-quoted string
switch(ch)
{
case '"': state = STATE_ARGUMENT; break;
case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break;
default: curarg += ch;
}
break;
case STATE_ESCAPE_OUTER: // '\' outside quotes
curarg += ch; state = STATE_ARGUMENT;
break;
case STATE_ESCAPE_SINGLEQUOTED: // '\' in single-quoted text
if(ch != '\'') curarg += '\\'; // keep '\' for everything but the quote
curarg += ch; state = STATE_SINGLEQUOTED;
break;
case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
if(ch != '"') curarg += '\\'; // keep '\' for everything but the quote
curarg += ch; state = STATE_DOUBLEQUOTED;
break;
}
}
switch(state) // final state
{
case STATE_EATING_SPACES:
return true;
case STATE_ARGUMENT:
args.push_back(curarg);
return true;
default: // ERROR to end in one of the other states
return false;
} }
}
try { void RPCExecutor::request(const QString &command)
{
std::vector<std::string> args;
if(!parseCommandLine(args, command.toStdString()))
{
emit reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
return;
}
if(args.empty())
return; // Nothing to do
try
{
std::string strPrint; std::string strPrint;
json_spirit::Value result = tableRPC.execute(strMethod, RPCConvertValues(strMethod, strParams)); // Convert argument list to JSON objects in method-dependent way,
// and pass it along with the method name to the dispatcher.
json_spirit::Value result = tableRPC.execute(
args[0],
RPCConvertValues(args[0], std::vector<std::string>(args.begin() + 1, args.end())));
// Format result reply // Format result reply
if (result.type() == json_spirit::null_type) if (result.type() == json_spirit::null_type)
@ -95,8 +174,18 @@ void RPCExecutor::request(const QString &command)
} }
catch (json_spirit::Object& objError) catch (json_spirit::Object& objError)
{ {
try // Nice formatting for standard-format error
{
int code = find_value(objError, "code").get_int();
std::string message = find_value(objError, "message").get_str();
emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
}
catch(std::runtime_error &) // raised when converting to invalid type, i.e. missing code or message
{
// Show raw JSON object
emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(write_string(json_spirit::Value(objError), false))); emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(write_string(json_spirit::Value(objError), false)));
} }
}
catch (std::exception& e) catch (std::exception& e)
{ {
emit reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what())); emit reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));

Loading…
Cancel
Save