Fix RPC console parser to handle escaped arguments more like bash
- Fix issue #1750
This commit is contained in:
parent
a65b53bf06
commit
576b5efe93
1 changed files with 98 additions and 20 deletions
|
@ -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,113 @@ void RPCExecutor::start()
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
enum CmdParseState
|
||||||
|
{
|
||||||
|
STATE_EATING_SPACES,
|
||||||
|
STATE_ARGUMENT,
|
||||||
|
STATE_SINGLEQUOTED,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_SINGLEQUOTED: // Single-quoted string
|
||||||
|
switch(ch)
|
||||||
|
{
|
||||||
|
case '\'': state = STATE_ARGUMENT; break;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RPCExecutor::request(const QString &command)
|
void RPCExecutor::request(const QString &command)
|
||||||
{
|
{
|
||||||
// Parse shell-like command line into separate arguments
|
std::vector<std::string> args;
|
||||||
std::string strMethod;
|
if(!parseCommandLine(args, command.toStdString()))
|
||||||
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
|
emit reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
|
||||||
strMethod = *beg;
|
|
||||||
else
|
|
||||||
strParams.push_back(*beg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(boost::escaped_list_error &e)
|
|
||||||
{
|
|
||||||
emit reply(RPCConsole::CMD_ERROR, QString("Parse error"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(args.empty())
|
||||||
|
return; // Nothing to do
|
||||||
try {
|
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)
|
||||||
|
|
Loading…
Reference in a new issue