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.cpp198
1 files changed, 148 insertions, 50 deletions
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 47af6a5724..38fe659c33 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -31,6 +31,7 @@
#include <QKeyEvent>
#include <QMenu>
+#include <QMessageBox>
#include <QScrollBar>
#include <QSettings>
#include <QSignalMapper>
@@ -63,6 +64,20 @@ const struct {
{NULL, NULL}
};
+namespace {
+
+// don't add private key handling cmd's to the history
+const QStringList historyFilter = QStringList()
+ << "importprivkey"
+ << "importmulti"
+ << "signmessagewithprivkey"
+ << "signrawtransaction"
+ << "walletpassphrase"
+ << "walletpassphrasechange"
+ << "encryptwallet";
+
+}
+
/* Object for executing console RPC commands in a separate thread.
*/
class RPCExecutor : public QObject
@@ -113,10 +128,10 @@ public:
#include "rpcconsole.moc"
/**
- * Split shell command line into a list of arguments and execute the command(s).
+ * Split shell command line into a list of arguments and optionally execute the command(s).
* Aims to emulate \c bash and friends.
*
- * - Command nesting is possible with brackets [example: validateaddress(getnewaddress())]
+ * - Command nesting is possible with parenthesis; for example: validateaddress(getnewaddress())
* - Arguments are delimited with whitespace or comma
* - Extra whitespace at the beginning and end and between arguments will be ignored
* - Text can be "double" or 'single' quoted
@@ -127,9 +142,11 @@ public:
*
* @param[out] result stringified Result from the executed command(chain)
* @param[in] strCommand Command line to split
+ * @param[in] fExecute set true if you want the command to be executed
+ * @param[out] pstrFilteredOut Command line, filtered to remove any sensitive data
*/
-bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string &strCommand)
+bool RPCConsole::RPCParseCommandLine(std::string &strResult, const std::string &strCommand, const bool fExecute, std::string * const pstrFilteredOut)
{
std::vector< std::vector<std::string> > stack;
stack.push_back(std::vector<std::string>());
@@ -137,6 +154,8 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
enum CmdParseState
{
STATE_EATING_SPACES,
+ STATE_EATING_SPACES_IN_ARG,
+ STATE_EATING_SPACES_IN_BRACKETS,
STATE_ARGUMENT,
STATE_SINGLEQUOTED,
STATE_DOUBLEQUOTED,
@@ -147,12 +166,35 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
} state = STATE_EATING_SPACES;
std::string curarg;
UniValue lastResult;
+ unsigned nDepthInsideSensitive = 0;
+ size_t filter_begin_pos = 0, chpos;
+ std::vector<std::pair<size_t, size_t>> filter_ranges;
+
+ auto add_to_current_stack = [&](const std::string& curarg) {
+ if (stack.back().empty() && (!nDepthInsideSensitive) && historyFilter.contains(QString::fromStdString(curarg), Qt::CaseInsensitive)) {
+ nDepthInsideSensitive = 1;
+ filter_begin_pos = chpos;
+ }
+ stack.back().push_back(curarg);
+ };
+
+ auto close_out_params = [&]() {
+ if (nDepthInsideSensitive) {
+ if (!--nDepthInsideSensitive) {
+ assert(filter_begin_pos);
+ filter_ranges.push_back(std::make_pair(filter_begin_pos, chpos));
+ filter_begin_pos = 0;
+ }
+ }
+ stack.pop_back();
+ };
std::string strCommandTerminated = strCommand;
if (strCommandTerminated.back() != '\n')
strCommandTerminated += "\n";
- for(char ch: strCommandTerminated)
+ for (chpos = 0; chpos < strCommandTerminated.size(); ++chpos)
{
+ char ch = strCommandTerminated[chpos];
switch(state)
{
case STATE_COMMAND_EXECUTED_INNER:
@@ -171,7 +213,7 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
curarg += ch;
break;
}
- if (curarg.size())
+ if (curarg.size() && fExecute)
{
// if we have a value query, query arrays with index and objects with a string key
UniValue subelement;
@@ -196,7 +238,7 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
breakParsing = false;
// pop the stack and return the result to the current command arguments
- stack.pop_back();
+ close_out_params();
// don't stringify the json in case of a string to avoid doublequotes
if (lastResult.isStr())
@@ -208,7 +250,7 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
if (curarg.size())
{
if (stack.size())
- stack.back().push_back(curarg);
+ add_to_current_stack(curarg);
else
strResult = curarg;
}
@@ -220,6 +262,8 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
break;
}
case STATE_ARGUMENT: // In or after argument
+ case STATE_EATING_SPACES_IN_ARG:
+ case STATE_EATING_SPACES_IN_BRACKETS:
case STATE_EATING_SPACES: // Handle runs of whitespace
switch(ch)
{
@@ -227,41 +271,55 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
case '\'': state = STATE_SINGLEQUOTED; break;
case '\\': state = STATE_ESCAPE_OUTER; break;
case '(': case ')': case '\n':
+ if (state == STATE_EATING_SPACES_IN_ARG)
+ throw std::runtime_error("Invalid Syntax");
if (state == STATE_ARGUMENT)
{
if (ch == '(' && stack.size() && stack.back().size() > 0)
- stack.push_back(std::vector<std::string>());
- if (curarg.size())
{
- // don't allow commands after executed commands on baselevel
- if (!stack.size())
- throw std::runtime_error("Invalid Syntax");
- stack.back().push_back(curarg);
+ if (nDepthInsideSensitive) {
+ ++nDepthInsideSensitive;
+ }
+ stack.push_back(std::vector<std::string>());
}
+
+ // don't allow commands after executed commands on baselevel
+ if (!stack.size())
+ throw std::runtime_error("Invalid Syntax");
+
+ add_to_current_stack(curarg);
curarg.clear();
- state = STATE_EATING_SPACES;
+ state = STATE_EATING_SPACES_IN_BRACKETS;
}
if ((ch == ')' || ch == '\n') && stack.size() > 0)
{
- std::string strPrint;
- // Convert argument list to JSON objects in method-dependent way,
- // and pass it along with the method name to the dispatcher.
- JSONRPCRequest req;
- req.params = RPCConvertValues(stack.back()[0], std::vector<std::string>(stack.back().begin() + 1, stack.back().end()));
- req.strMethod = stack.back()[0];
- lastResult = tableRPC.execute(req);
+ if (fExecute) {
+ // Convert argument list to JSON objects in method-dependent way,
+ // and pass it along with the method name to the dispatcher.
+ JSONRPCRequest req;
+ req.params = RPCConvertValues(stack.back()[0], std::vector<std::string>(stack.back().begin() + 1, stack.back().end()));
+ req.strMethod = stack.back()[0];
+ lastResult = tableRPC.execute(req);
+ }
state = STATE_COMMAND_EXECUTED;
curarg.clear();
}
break;
case ' ': case ',': case '\t':
- if(state == STATE_ARGUMENT) // Space ends argument
+ if(state == STATE_EATING_SPACES_IN_ARG && curarg.empty() && ch == ',')
+ throw std::runtime_error("Invalid Syntax");
+
+ else if(state == STATE_ARGUMENT) // Space ends argument
{
- if (curarg.size())
- stack.back().push_back(curarg);
+ add_to_current_stack(curarg);
curarg.clear();
}
+ if ((state == STATE_EATING_SPACES_IN_BRACKETS || state == STATE_ARGUMENT) && ch == ',')
+ {
+ state = STATE_EATING_SPACES_IN_ARG;
+ break;
+ }
state = STATE_EATING_SPACES;
break;
default: curarg += ch; state = STATE_ARGUMENT;
@@ -291,6 +349,16 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
break;
}
}
+ if (pstrFilteredOut) {
+ if (STATE_COMMAND_EXECUTED == state) {
+ assert(!stack.empty());
+ close_out_params();
+ }
+ *pstrFilteredOut = strCommand;
+ for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) {
+ pstrFilteredOut->replace(i->first, i->second - i->first, "(…)");
+ }
+ }
switch(state) // final state
{
case STATE_COMMAND_EXECUTED:
@@ -382,7 +450,6 @@ RPCConsole::RPCConsole(const PlatformStyle *_platformStyle, QWidget *parent) :
// based timer interface
RPCSetTimerInterfaceIfUnset(rpcTimerInterface);
- startExecutor();
setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
ui->detailWidget->hide();
@@ -396,7 +463,6 @@ RPCConsole::RPCConsole(const PlatformStyle *_platformStyle, QWidget *parent) :
RPCConsole::~RPCConsole()
{
GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this);
- Q_EMIT stopExecutor();
RPCUnsetTimerInterface(rpcTimerInterface);
delete rpcTimerInterface;
delete ui;
@@ -486,7 +552,7 @@ void RPCConsole::setClientModel(ClientModel *model)
QAction* banAction365d = new QAction(tr("Ban for") + " " + tr("1 &year"), this);
// create peer table context menu
- peersTableContextMenu = new QMenu();
+ peersTableContextMenu = new QMenu(this);
peersTableContextMenu->addAction(disconnectAction);
peersTableContextMenu->addAction(banAction1h);
peersTableContextMenu->addAction(banAction24h);
@@ -517,7 +583,7 @@ void RPCConsole::setClientModel(ClientModel *model)
// peer table signal handling - update peer details when new nodes are added to the model
connect(model->getPeerTableModel(), SIGNAL(layoutChanged()), this, SLOT(peerLayoutChanged()));
// peer table signal handling - cache selected node ids
- connect(model->getPeerTableModel(), SIGNAL(layoutAboutToChange()), this, SLOT(peerLayoutAboutToChange()));
+ connect(model->getPeerTableModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(peerLayoutAboutToChange()));
// set up ban table
ui->banlistWidget->setModel(model->getBanTableModel());
@@ -534,7 +600,7 @@ void RPCConsole::setClientModel(ClientModel *model)
QAction* unbanAction = new QAction(tr("&Unban"), this);
// create ban table context menu
- banTableContextMenu = new QMenu();
+ banTableContextMenu = new QMenu(this);
banTableContextMenu->addAction(unbanAction);
// ban table context menu signals
@@ -565,6 +631,14 @@ void RPCConsole::setClientModel(ClientModel *model)
autoCompleter = new QCompleter(wordList, this);
ui->lineEdit->setCompleter(autoCompleter);
autoCompleter->popup()->installEventFilter(this);
+ // Start thread to execute RPC commands.
+ startExecutor();
+ }
+ if (!model) {
+ // Client model is being set to 0, this means shutdown() is about to be called.
+ // Make sure we clean up the executor thread
+ Q_EMIT stopExecutor();
+ thread.wait();
}
}
@@ -644,13 +718,18 @@ void RPCConsole::clear(bool clearHistory)
"td.message { font-family: %1; font-size: %2; white-space:pre-wrap; } "
"td.cmd-request { color: #006060; } "
"td.cmd-error { color: red; } "
+ ".secwarning { color: red; }"
"b { color: #006060; } "
).arg(fixedFontInfo.family(), QString("%1pt").arg(consoleFontSize))
);
message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(tr(PACKAGE_NAME)) + "<br>" +
tr("Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen.") + "<br>" +
- tr("Type <b>help</b> for an overview of available commands.")), true);
+ tr("Type <b>help</b> for an overview of available commands.")) +
+ "<br><span class=\"secwarning\">" +
+ tr("WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramification of a command.") +
+ "</span>",
+ true);
}
void RPCConsole::keyPressEvent(QKeyEvent *event)
@@ -724,12 +803,30 @@ void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage)
void RPCConsole::on_lineEdit_returnPressed()
{
QString cmd = ui->lineEdit->text();
- ui->lineEdit->clear();
if(!cmd.isEmpty())
{
+ std::string strFilteredCmd;
+ try {
+ std::string dummy;
+ if (!RPCParseCommandLine(dummy, cmd.toStdString(), false, &strFilteredCmd)) {
+ // Failed to parse command, so we cannot even filter it for the history
+ throw std::runtime_error("Invalid command line");
+ }
+ } catch (const std::exception& e) {
+ QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what()));
+ return;
+ }
+
+ ui->lineEdit->clear();
+
+ cmdBeforeBrowsing = QString();
+
message(CMD_REQUEST, cmd);
Q_EMIT cmdRequest(cmd);
+
+ cmd = QString::fromStdString(strFilteredCmd);
+
// Remove command, if already in history
history.removeOne(cmd);
// Append command to history
@@ -739,6 +836,7 @@ void RPCConsole::on_lineEdit_returnPressed()
history.removeFirst();
// Set pointer to end of history
historyPtr = history.size();
+
// Scroll console view to end
scrollToEnd();
}
@@ -746,6 +844,11 @@ void RPCConsole::on_lineEdit_returnPressed()
void RPCConsole::browseHistory(int offset)
{
+ // store current text when start browsing through the history
+ if (historyPtr == history.size()) {
+ cmdBeforeBrowsing = ui->lineEdit->text();
+ }
+
historyPtr += offset;
if(historyPtr < 0)
historyPtr = 0;
@@ -754,14 +857,16 @@ void RPCConsole::browseHistory(int offset)
QString cmd;
if(historyPtr < history.size())
cmd = history.at(historyPtr);
+ else if (!cmdBeforeBrowsing.isNull()) {
+ cmd = cmdBeforeBrowsing;
+ }
ui->lineEdit->setText(cmd);
}
void RPCConsole::startExecutor()
{
- QThread *thread = new QThread;
RPCExecutor *executor = new RPCExecutor();
- executor->moveToThread(thread);
+ executor->moveToThread(&thread);
// Replies from executor object must go to this object
connect(executor, SIGNAL(reply(int,QString)), this, SLOT(message(int,QString)));
@@ -769,16 +874,14 @@ void RPCConsole::startExecutor()
connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString)));
// On stopExecutor signal
- // - queue executor for deletion (in execution thread)
// - quit the Qt event loop in the execution thread
- connect(this, SIGNAL(stopExecutor()), executor, SLOT(deleteLater()));
- connect(this, SIGNAL(stopExecutor()), thread, SLOT(quit()));
- // Queue the thread for deletion (in this thread) when it is finished
- connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
+ connect(this, SIGNAL(stopExecutor()), &thread, SLOT(quit()));
+ // - queue executor for deletion (in execution thread)
+ connect(&thread, SIGNAL(finished()), executor, SLOT(deleteLater()), Qt::DirectConnection);
// Default implementation of QThread::run() simply spins up an event loop in the thread,
// which is what we want.
- thread->start();
+ thread.start();
}
void RPCConsole::on_tabWidget_currentChanged(int index)
@@ -1004,11 +1107,11 @@ void RPCConsole::disconnectSelectedNode()
return;
// Get selected peer addresses
- QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, 0);
+ QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
for(int i = 0; i < nodes.count(); i++)
{
// Get currently selected peer address
- NodeId id = nodes.at(i).data(PeerTableModel::NetNodeId).toInt();
+ NodeId id = nodes.at(i).data().toInt();
// Find the node, disconnect it and clear the selected node
if(g_connman->DisconnectNode(id))
clearSelectedNode();
@@ -1021,11 +1124,11 @@ void RPCConsole::banSelectedNode(int bantime)
return;
// Get selected peer addresses
- QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, 0);
+ QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
for(int i = 0; i < nodes.count(); i++)
{
// Get currently selected peer address
- NodeId id = nodes.at(i).data(PeerTableModel::NetNodeId).toInt();
+ NodeId id = nodes.at(i).data().toInt();
// Get currently selected peer address
int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id);
@@ -1048,11 +1151,11 @@ void RPCConsole::unbanSelectedNode()
return;
// Get selected ban addresses
- QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->banlistWidget, 0);
+ QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->banlistWidget, BanTableModel::Address);
for(int i = 0; i < nodes.count(); i++)
{
// Get currently selected ban address
- QString strNode = nodes.at(i).data(BanTableModel::Address).toString();
+ QString strNode = nodes.at(i).data().toString();
CSubNet possibleSubnet;
LookupSubNet(strNode.toStdString().c_str(), possibleSubnet);
@@ -1086,8 +1189,3 @@ void RPCConsole::setTabFocus(enum TabTypes tabType)
{
ui->tabWidget->setCurrentIndex(tabType);
}
-
-void RPCConsole::on_toggleNetworkActiveButton_clicked()
-{
- clientModel->setNetworkActive(!clientModel->getNetworkActive());
-}