aboutsummaryrefslogtreecommitdiff
path: root/src/qt/rpcconsole.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qt/rpcconsole.cpp')
-rw-r--r--src/qt/rpcconsole.cpp160
1 files changed, 131 insertions, 29 deletions
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 08f936e719..2b8a0c049b 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,108 @@ 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
+ * - Text can be "double" or 'single' quoted
+ * - The backslash \c \ is used as escape character
+ * - Outside quotes, any character can be escaped
+ * - Within double quotes, only escape \c " and backslashes before a \c " or another backslash
+ * - Within single quotes, no escaping is possible and no special interpretation takes place
+ *
+ * @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_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: // In or after argument
+ case STATE_EATING_SPACES: // Handle runs of whitespace
+ 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;
+ 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_DOUBLEQUOTED: // '\' in double-quoted text
+ if(ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
+ 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 +168,16 @@ 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)
{
@@ -110,13 +192,14 @@ RPCConsole::RPCConsole(QWidget *parent) :
{
ui->setupUi(this);
-#ifndef Q_WS_MAC
+#ifndef Q_OS_MAC
ui->openDebugLogfileButton->setIcon(QIcon(":/icons/export"));
ui->showCLOptionsButton->setIcon(QIcon(":/icons/options"));
#endif
// Install event filter for up and down arrow
ui->lineEdit->installEventFilter(this);
+ ui->messagesWidget->installEventFilter(this);
connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
@@ -136,15 +219,34 @@ RPCConsole::~RPCConsole()
bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
{
- if(obj == ui->lineEdit)
+ if(event->type() == QEvent::KeyPress) // Special key handling
{
- if(event->type() == QEvent::KeyPress)
+ QKeyEvent *keyevt = static_cast<QKeyEvent*>(event);
+ int key = keyevt->key();
+ Qt::KeyboardModifiers mod = keyevt->modifiers();
+ switch(key)
{
- QKeyEvent *key = static_cast<QKeyEvent*>(event);
- switch(key->key())
+ case Qt::Key_Up: if(obj == ui->lineEdit) { browseHistory(-1); return true; } break;
+ case Qt::Key_Down: if(obj == ui->lineEdit) { browseHistory(1); return true; } break;
+ case Qt::Key_PageUp: /* pass paging keys to messages widget */
+ case Qt::Key_PageDown:
+ if(obj == ui->lineEdit)
+ {
+ QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt));
+ return true;
+ }
+ break;
+ default:
+ // Typing in messages widget brings focus to line edit, and redirects key there
+ // Exclude most combinations and keys that emit no text, except paste shortcuts
+ if(obj == ui->messagesWidget && (
+ (!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) ||
+ ((mod & Qt::ControlModifier) && key == Qt::Key_V) ||
+ ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert)))
{
- case Qt::Key_Up: browseHistory(-1); return true;
- case Qt::Key_Down: browseHistory(1); return true;
+ ui->lineEdit->setFocus();
+ QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt));
+ return true;
}
}
}