31
31
32
32
#include < QKeyEvent>
33
33
#include < QMenu>
34
+ #include < QMessageBox>
34
35
#include < QScrollBar>
35
36
#include < QSettings>
36
37
#include < QSignalMapper>
@@ -63,6 +64,20 @@ const struct {
63
64
{NULL , NULL }
64
65
};
65
66
67
+ namespace {
68
+
69
+ // don't add private key handling cmd's to the history
70
+ const QStringList historyFilter = QStringList()
71
+ << " importprivkey"
72
+ << " importmulti"
73
+ << " signmessagewithprivkey"
74
+ << " signrawtransaction"
75
+ << " walletpassphrase"
76
+ << " walletpassphrasechange"
77
+ << " encryptwallet" ;
78
+
79
+ }
80
+
66
81
/* Object for executing console RPC commands in a separate thread.
67
82
*/
68
83
class RPCExecutor : public QObject
@@ -113,10 +128,10 @@ class QtRPCTimerInterface: public RPCTimerInterface
113
128
#include " rpcconsole.moc"
114
129
115
130
/* *
116
- * Split shell command line into a list of arguments and execute the command(s).
131
+ * Split shell command line into a list of arguments and optionally execute the command(s).
117
132
* Aims to emulate \c bash and friends.
118
133
*
119
- * - Command nesting is possible with brackets [ example: validateaddress(getnewaddress())]
134
+ * - Command nesting is possible with parenthesis; for example: validateaddress(getnewaddress())
120
135
* - Arguments are delimited with whitespace or comma
121
136
* - Extra whitespace at the beginning and end and between arguments will be ignored
122
137
* - Text can be "double" or 'single' quoted
@@ -127,9 +142,11 @@ class QtRPCTimerInterface: public RPCTimerInterface
127
142
*
128
143
* @param[out] result stringified Result from the executed command(chain)
129
144
* @param[in] strCommand Command line to split
145
+ * @param[in] fExecute set true if you want the command to be executed
146
+ * @param[out] pstrFilteredOut Command line, filtered to remove any sensitive data
130
147
*/
131
148
132
- bool RPCConsole::RPCExecuteCommandLine (std::string &strResult, const std::string &strCommand)
149
+ bool RPCConsole::RPCParseCommandLine (std::string &strResult, const std::string &strCommand, const bool fExecute , std::string * const pstrFilteredOut )
133
150
{
134
151
std::vector< std::vector<std::string> > stack;
135
152
stack.push_back (std::vector<std::string>());
@@ -149,12 +166,35 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
149
166
} state = STATE_EATING_SPACES;
150
167
std::string curarg;
151
168
UniValue lastResult;
169
+ unsigned nDepthInsideSensitive = 0 ;
170
+ size_t filter_begin_pos = 0 , chpos;
171
+ std::vector<std::pair<size_t , size_t >> filter_ranges;
172
+
173
+ auto add_to_current_stack = [&](const std::string& curarg) {
174
+ if (stack.back ().empty () && (!nDepthInsideSensitive) && historyFilter.contains (QString::fromStdString (curarg), Qt::CaseInsensitive)) {
175
+ nDepthInsideSensitive = 1 ;
176
+ filter_begin_pos = chpos;
177
+ }
178
+ stack.back ().push_back (curarg);
179
+ };
180
+
181
+ auto close_out_params = [&]() {
182
+ if (nDepthInsideSensitive) {
183
+ if (!--nDepthInsideSensitive) {
184
+ assert (filter_begin_pos);
185
+ filter_ranges.push_back (std::make_pair (filter_begin_pos, chpos));
186
+ filter_begin_pos = 0 ;
187
+ }
188
+ }
189
+ stack.pop_back ();
190
+ };
152
191
153
192
std::string strCommandTerminated = strCommand;
154
193
if (strCommandTerminated.back () != ' \n ' )
155
194
strCommandTerminated += " \n " ;
156
- for ( char ch: strCommandTerminated)
195
+ for (chpos = 0 ; chpos < strCommandTerminated. size (); ++chpos )
157
196
{
197
+ char ch = strCommandTerminated[chpos];
158
198
switch (state)
159
199
{
160
200
case STATE_COMMAND_EXECUTED_INNER:
@@ -173,7 +213,7 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
173
213
curarg += ch;
174
214
break ;
175
215
}
176
- if (curarg.size ())
216
+ if (curarg.size () && fExecute )
177
217
{
178
218
// if we have a value query, query arrays with index and objects with a string key
179
219
UniValue subelement;
@@ -198,7 +238,7 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
198
238
breakParsing = false ;
199
239
200
240
// pop the stack and return the result to the current command arguments
201
- stack. pop_back ();
241
+ close_out_params ();
202
242
203
243
// don't stringify the json in case of a string to avoid doublequotes
204
244
if (lastResult.isStr ())
@@ -210,7 +250,7 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
210
250
if (curarg.size ())
211
251
{
212
252
if (stack.size ())
213
- stack. back (). push_back (curarg);
253
+ add_to_current_stack (curarg);
214
254
else
215
255
strResult = curarg;
216
256
}
@@ -236,25 +276,31 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
236
276
if (state == STATE_ARGUMENT)
237
277
{
238
278
if (ch == ' (' && stack.size () && stack.back ().size () > 0 )
279
+ {
280
+ if (nDepthInsideSensitive) {
281
+ ++nDepthInsideSensitive;
282
+ }
239
283
stack.push_back (std::vector<std::string>());
284
+ }
240
285
241
286
// don't allow commands after executed commands on baselevel
242
287
if (!stack.size ())
243
288
throw std::runtime_error (" Invalid Syntax" );
244
289
245
- stack. back (). push_back (curarg);
290
+ add_to_current_stack (curarg);
246
291
curarg.clear ();
247
292
state = STATE_EATING_SPACES_IN_BRACKETS;
248
293
}
249
294
if ((ch == ' )' || ch == ' \n ' ) && stack.size () > 0 )
250
295
{
251
- std::string strPrint;
252
- // Convert argument list to JSON objects in method-dependent way,
253
- // and pass it along with the method name to the dispatcher.
254
- JSONRPCRequest req;
255
- req.params = RPCConvertValues (stack.back ()[0 ], std::vector<std::string>(stack.back ().begin () + 1 , stack.back ().end ()));
256
- req.strMethod = stack.back ()[0 ];
257
- lastResult = tableRPC.execute (req);
296
+ if (fExecute ) {
297
+ // Convert argument list to JSON objects in method-dependent way,
298
+ // and pass it along with the method name to the dispatcher.
299
+ JSONRPCRequest req;
300
+ req.params = RPCConvertValues (stack.back ()[0 ], std::vector<std::string>(stack.back ().begin () + 1 , stack.back ().end ()));
301
+ req.strMethod = stack.back ()[0 ];
302
+ lastResult = tableRPC.execute (req);
303
+ }
258
304
259
305
state = STATE_COMMAND_EXECUTED;
260
306
curarg.clear ();
@@ -266,7 +312,7 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
266
312
267
313
else if (state == STATE_ARGUMENT) // Space ends argument
268
314
{
269
- stack. back (). push_back (curarg);
315
+ add_to_current_stack (curarg);
270
316
curarg.clear ();
271
317
}
272
318
if ((state == STATE_EATING_SPACES_IN_BRACKETS || state == STATE_ARGUMENT) && ch == ' ,' )
@@ -303,6 +349,16 @@ bool RPCConsole::RPCExecuteCommandLine(std::string &strResult, const std::string
303
349
break ;
304
350
}
305
351
}
352
+ if (pstrFilteredOut) {
353
+ if (STATE_COMMAND_EXECUTED == state) {
354
+ assert (!stack.empty ());
355
+ close_out_params ();
356
+ }
357
+ *pstrFilteredOut = strCommand;
358
+ for (auto i = filter_ranges.rbegin (); i != filter_ranges.rend (); ++i) {
359
+ pstrFilteredOut->replace (i->first , i->second - i->first , " (…)" );
360
+ }
361
+ }
306
362
switch (state) // final state
307
363
{
308
364
case STATE_COMMAND_EXECUTED:
@@ -747,12 +803,30 @@ void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage)
747
803
void RPCConsole::on_lineEdit_returnPressed ()
748
804
{
749
805
QString cmd = ui->lineEdit ->text ();
750
- ui->lineEdit ->clear ();
751
806
752
807
if (!cmd.isEmpty ())
753
808
{
809
+ std::string strFilteredCmd;
810
+ try {
811
+ std::string dummy;
812
+ if (!RPCParseCommandLine (dummy, cmd.toStdString (), false , &strFilteredCmd)) {
813
+ // Failed to parse command, so we cannot even filter it for the history
814
+ throw std::runtime_error (" Invalid command line" );
815
+ }
816
+ } catch (const std::exception& e) {
817
+ QMessageBox::critical (this , " Error" , QString (" Error: " ) + QString::fromStdString (e.what ()));
818
+ return ;
819
+ }
820
+
821
+ ui->lineEdit ->clear ();
822
+
823
+ cmdBeforeBrowsing = QString ();
824
+
754
825
message (CMD_REQUEST, cmd);
755
826
Q_EMIT cmdRequest (cmd);
827
+
828
+ cmd = QString::fromStdString (strFilteredCmd);
829
+
756
830
// Remove command, if already in history
757
831
history.removeOne (cmd);
758
832
// Append command to history
@@ -762,13 +836,19 @@ void RPCConsole::on_lineEdit_returnPressed()
762
836
history.removeFirst ();
763
837
// Set pointer to end of history
764
838
historyPtr = history.size ();
839
+
765
840
// Scroll console view to end
766
841
scrollToEnd ();
767
842
}
768
843
}
769
844
770
845
void RPCConsole::browseHistory (int offset)
771
846
{
847
+ // store current text when start browsing through the history
848
+ if (historyPtr == history.size ()) {
849
+ cmdBeforeBrowsing = ui->lineEdit ->text ();
850
+ }
851
+
772
852
historyPtr += offset;
773
853
if (historyPtr < 0 )
774
854
historyPtr = 0 ;
@@ -777,6 +857,9 @@ void RPCConsole::browseHistory(int offset)
777
857
QString cmd;
778
858
if (historyPtr < history.size ())
779
859
cmd = history.at (historyPtr);
860
+ else if (!cmdBeforeBrowsing.isNull ()) {
861
+ cmd = cmdBeforeBrowsing;
862
+ }
780
863
ui->lineEdit ->setText (cmd);
781
864
}
782
865
0 commit comments