From 576b5efe93cdcd544c205f4c4f8f1696e6e907ee Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Thu, 30 Aug 2012 21:42:18 +0200 Subject: [PATCH 1/2] Fix RPC console parser to handle escaped arguments more like bash - Fix issue #1750 --- src/qt/rpcconsole.cpp | 118 +++++++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 20 deletions(-) diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 08f936e71..470eba732 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -13,7 +13,6 @@ #include #include -#include #include // TODO: make it possible to filter out categories (esp debug messages when implemented) @@ -54,34 +53,113 @@ 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 &args, const std::string &strCommand) { - // Parse shell-like command line into separate arguments - std::string strMethod; - std::vector strParams; - try { - boost::escaped_list_separator els('\\',' ','\"'); - std::string strCommand = command.toStdString(); - boost::tokenizer > tok(strCommand, els); - - int n = 0; - for(boost::tokenizer >::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; } +} +void RPCExecutor::request(const QString &command) +{ + std::vector 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(args.begin() + 1, args.end()))); // Format result reply if (result.type() == json_spirit::null_type) From b5c1467a7d7d261de5f87d6f26a00cf5ab230dfb Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 31 Aug 2012 17:40:13 +0200 Subject: [PATCH 2/2] In RPC console, attempt to format errors Try to display a nicer message instead of dumping raw JSON object when possible. If the error somehow doesn't have the required 'code' and 'message' fields, fall back to printing raw JSON object. --- src/qt/rpcconsole.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 470eba732..6e48fbe8d 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -153,7 +153,8 @@ void RPCExecutor::request(const QString &command) } if(args.empty()) return; // Nothing to do - try { + try + { std::string strPrint; // Convert argument list to JSON objects in method-dependent way, // and pass it along with the method name to the dispatcher. @@ -173,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) {