diff options
author | Gregory Maxwell <greg@xiph.org> | 2012-08-31 22:50:23 -0700 |
---|---|---|
committer | Gregory Maxwell <greg@xiph.org> | 2012-08-31 22:50:23 -0700 |
commit | ddbddcb31e298ad94f45547a72d0687ca323daa5 (patch) | |
tree | fa70bbbe0ff7d444dbaa9639c16131126185af5d /src/qt | |
parent | c2cd13faa4a09214c8d262f9e662edb57fc7b171 (diff) | |
parent | b5c1467a7d7d261de5f87d6f26a00cf5ab230dfb (diff) | |
download | bitcoin-ddbddcb31e298ad94f45547a72d0687ca323daa5.tar.xz |
Merge pull request #1758 from laanwj/2012_08_uiconsole_parsing
Fix RPC console parser to handle escaped arguments more like bash
Diffstat (limited to 'src/qt')
-rw-r--r-- | src/qt/rpcconsole.cpp | 133 |
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) { |