|
|
@ -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())); |
|
|
|