aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Maxwell <greg@xiph.org>2012-08-31 22:50:23 -0700
committerGregory Maxwell <greg@xiph.org>2012-08-31 22:50:23 -0700
commitddbddcb31e298ad94f45547a72d0687ca323daa5 (patch)
treefa70bbbe0ff7d444dbaa9639c16131126185af5d
parentc2cd13faa4a09214c8d262f9e662edb57fc7b171 (diff)
parentb5c1467a7d7d261de5f87d6f26a00cf5ab230dfb (diff)
downloadbitcoin-ddbddcb31e298ad94f45547a72d0687ca323daa5.tar.xz
Merge pull request #1758 from laanwj/2012_08_uiconsole_parsing
Fix RPC console parser to handle escaped arguments more like bash
-rw-r--r--src/qt/rpcconsole.cpp133
1 files changed, 111 insertions, 22 deletions
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 08f936e719..6e48fbe8da 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -13,7 +13,6 @@
#include <QUrl>
#include <QScrollBar>
-#include <boost/tokenizer.hpp>
#include <openssl/crypto.h>
// TODO: make it possible to filter out categories (esp debug messages when implemented)
@@ -54,34 +53,114 @@ void RPCExecutor::start()
// 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
- 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)
+ 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)
{
- if(n == 0) // First parameter is the command
- strMethod = *beg;
- else
- strParams.push_back(*beg);
+ 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;
}
}
- catch(boost::escaped_list_error &e)
+ switch(state) // final state
{
- emit reply(RPCConsole::CMD_ERROR, QString("Parse error"));
- return;
+ 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;
- 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
if (result.type() == json_spirit::null_type)
@@ -95,7 +174,17 @@ void RPCExecutor::request(const QString &command)
}
catch (json_spirit::Object& objError)
{
- emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(write_string(json_spirit::Value(objError), false)));
+ 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)));
+ }
}
catch (std::exception& e)
{