@@ -113,117 +113,208 @@ class QtRPCTimerInterface: public RPCTimerInterface
113
113
#include " rpcconsole.moc"
114
114
115
115
/* *
116
- * Split shell command line into a list of arguments. Aims to emulate \c bash and friends.
116
+ * Split shell command line into a list of arguments and execute the command(s).
117
+ * Aims to emulate \c bash and friends.
117
118
*
118
- * - Arguments are delimited with whitespace
119
+ * - Command nesting is possible with brackets [example: validateaddress(getnewaddress())]
120
+ * - Arguments are delimited with whitespace or comma
119
121
* - Extra whitespace at the beginning and end and between arguments will be ignored
120
122
* - Text can be "double" or 'single' quoted
121
123
* - The backslash \c \ is used as escape character
122
124
* - Outside quotes, any character can be escaped
123
125
* - Within double quotes, only escape \c " and backslashes before a \c " or another backslash
124
126
* - Within single quotes, no escaping is possible and no special interpretation takes place
125
127
*
126
- * @param[out] args Parsed arguments will be appended to this list
128
+ * @param[out] result stringified Result from the executed command(chain)
127
129
* @param[in] strCommand Command line to split
128
130
*/
129
- bool parseCommandLine (std::vector<std::string> &args, const std::string &strCommand)
131
+
132
+ bool RPCConsole::RPCExecuteCommandLine (std::string &strResult, const std::string &strCommand)
130
133
{
134
+ std::vector< std::vector<std::string> > stack;
135
+ stack.push_back (std::vector<std::string>());
136
+
131
137
enum CmdParseState
132
138
{
133
139
STATE_EATING_SPACES,
134
140
STATE_ARGUMENT,
135
141
STATE_SINGLEQUOTED,
136
142
STATE_DOUBLEQUOTED,
137
143
STATE_ESCAPE_OUTER,
138
- STATE_ESCAPE_DOUBLEQUOTED
144
+ STATE_ESCAPE_DOUBLEQUOTED,
145
+ STATE_COMMAND_EXECUTED,
146
+ STATE_COMMAND_EXECUTED_INNER
139
147
} state = STATE_EATING_SPACES;
140
148
std::string curarg;
141
- Q_FOREACH (char ch, strCommand)
149
+ UniValue lastResult;
150
+
151
+ std::string strCommandTerminated = strCommand;
152
+ if (strCommandTerminated.back () != ' \n ' )
153
+ strCommandTerminated += " \n " ;
154
+ for (char ch: strCommandTerminated)
142
155
{
143
156
switch (state)
144
157
{
145
- case STATE_ARGUMENT: // In or after argument
146
- case STATE_EATING_SPACES: // Handle runs of whitespace
147
- switch (ch)
158
+ case STATE_COMMAND_EXECUTED_INNER:
159
+ case STATE_COMMAND_EXECUTED:
148
160
{
149
- case ' "' : state = STATE_DOUBLEQUOTED; break ;
150
- case ' \' ' : state = STATE_SINGLEQUOTED; break ;
151
- case ' \\ ' : state = STATE_ESCAPE_OUTER; break ;
152
- case ' ' : case ' \n ' : case ' \t ' :
153
- if (state == STATE_ARGUMENT) // Space ends argument
161
+ bool breakParsing = true ;
162
+ switch (ch)
154
163
{
155
- args.push_back (curarg);
156
- curarg.clear ();
164
+ case ' [' : curarg.clear (); state = STATE_COMMAND_EXECUTED_INNER; break ;
165
+ default :
166
+ if (state == STATE_COMMAND_EXECUTED_INNER)
167
+ {
168
+ if (ch != ' ]' )
169
+ {
170
+ // append char to the current argument (which is also used for the query command)
171
+ curarg += ch;
172
+ break ;
173
+ }
174
+ if (curarg.size ())
175
+ {
176
+ // if we have a value query, query arrays with index and objects with a string key
177
+ UniValue subelement;
178
+ if (lastResult.isArray ())
179
+ {
180
+ for (char argch: curarg)
181
+ if (!std::isdigit (argch))
182
+ throw std::runtime_error (" Invalid result query" );
183
+ subelement = lastResult[atoi (curarg.c_str ())];
184
+ }
185
+ else if (lastResult.isObject ())
186
+ subelement = find_value (lastResult, curarg);
187
+ else
188
+ throw std::runtime_error (" Invalid result query" ); // no array or object: abort
189
+ lastResult = subelement;
190
+ }
191
+
192
+ state = STATE_COMMAND_EXECUTED;
193
+ break ;
194
+ }
195
+ // don't break parsing when the char is required for the next argument
196
+ breakParsing = false ;
197
+
198
+ // pop the stack and return the result to the current command arguments
199
+ stack.pop_back ();
200
+
201
+ // don't stringify the json in case of a string to avoid doublequotes
202
+ if (lastResult.isStr ())
203
+ curarg = lastResult.get_str ();
204
+ else
205
+ curarg = lastResult.write (2 );
206
+
207
+ // if we have a non empty result, use it as stack argument otherwise as general result
208
+ if (curarg.size ())
209
+ {
210
+ if (stack.size ())
211
+ stack.back ().push_back (curarg);
212
+ else
213
+ strResult = curarg;
214
+ }
215
+ curarg.clear ();
216
+ // assume eating space state
217
+ state = STATE_EATING_SPACES;
157
218
}
158
- state = STATE_EATING_SPACES;
159
- break ;
160
- default : curarg += ch; state = STATE_ARGUMENT;
219
+ if (breakParsing)
220
+ break ;
161
221
}
162
- break ;
163
- case STATE_SINGLEQUOTED : // Single-quoted string
164
- switch (ch)
222
+ case STATE_ARGUMENT: // In or after argument
223
+ case STATE_EATING_SPACES : // Handle runs of whitespace
224
+ switch (ch)
165
225
{
166
- case ' \' ' : state = STATE_ARGUMENT; break ;
167
- default : curarg += ch;
226
+ case ' "' : state = STATE_DOUBLEQUOTED; break ;
227
+ case ' \' ' : state = STATE_SINGLEQUOTED; break ;
228
+ case ' \\ ' : state = STATE_ESCAPE_OUTER; break ;
229
+ case ' (' : case ' )' : case ' \n ' :
230
+ if (state == STATE_ARGUMENT)
231
+ {
232
+ if (ch == ' (' && stack.size () && stack.back ().size () > 0 )
233
+ stack.push_back (std::vector<std::string>());
234
+ if (curarg.size ())
235
+ {
236
+ // don't allow commands after executed commands on baselevel
237
+ if (!stack.size ())
238
+ throw std::runtime_error (" Invalid Syntax" );
239
+ stack.back ().push_back (curarg);
240
+ }
241
+ curarg.clear ();
242
+ state = STATE_EATING_SPACES;
243
+ }
244
+ if ((ch == ' )' || ch == ' \n ' ) && stack.size () > 0 )
245
+ {
246
+ std::string strPrint;
247
+ // Convert argument list to JSON objects in method-dependent way,
248
+ // and pass it along with the method name to the dispatcher.
249
+ lastResult = tableRPC.execute (stack.back ()[0 ], RPCConvertValues (stack.back ()[0 ], std::vector<std::string>(stack.back ().begin () + 1 , stack.back ().end ())));
250
+
251
+ state = STATE_COMMAND_EXECUTED;
252
+ curarg.clear ();
253
+ }
254
+ break ;
255
+ case ' ' : case ' ,' : case ' \t ' :
256
+ if (state == STATE_ARGUMENT) // Space ends argument
257
+ {
258
+ if (curarg.size ())
259
+ stack.back ().push_back (curarg);
260
+ curarg.clear ();
261
+ }
262
+ state = STATE_EATING_SPACES;
263
+ break ;
264
+ default : curarg += ch; state = STATE_ARGUMENT;
168
265
}
169
- break ;
170
- case STATE_DOUBLEQUOTED : // Double -quoted string
171
- switch (ch)
266
+ break ;
267
+ case STATE_SINGLEQUOTED : // Single -quoted string
268
+ switch (ch)
172
269
{
173
- case ' "' : state = STATE_ARGUMENT; break ;
174
- case ' \\ ' : state = STATE_ESCAPE_DOUBLEQUOTED; break ;
175
- default : curarg += ch;
270
+ case ' \' ' : state = STATE_ARGUMENT; break ;
271
+ default : curarg += ch;
176
272
}
177
- break ;
178
- case STATE_ESCAPE_OUTER: // '\' outside quotes
179
- curarg += ch; state = STATE_ARGUMENT;
180
- break ;
181
- case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
182
- if (ch != ' "' && ch != ' \\ ' ) curarg += ' \\ ' ; // keep '\' for everything but the quote and '\' itself
183
- curarg += ch; state = STATE_DOUBLEQUOTED;
184
- break ;
273
+ break ;
274
+ case STATE_DOUBLEQUOTED: // Double-quoted string
275
+ switch (ch)
276
+ {
277
+ case ' "' : state = STATE_ARGUMENT; break ;
278
+ case ' \\ ' : state = STATE_ESCAPE_DOUBLEQUOTED; break ;
279
+ default : curarg += ch;
280
+ }
281
+ break ;
282
+ case STATE_ESCAPE_OUTER: // '\' outside quotes
283
+ curarg += ch; state = STATE_ARGUMENT;
284
+ break ;
285
+ case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
286
+ if (ch != ' "' && ch != ' \\ ' ) curarg += ' \\ ' ; // keep '\' for everything but the quote and '\' itself
287
+ curarg += ch; state = STATE_DOUBLEQUOTED;
288
+ break ;
185
289
}
186
290
}
187
291
switch (state) // final state
188
292
{
189
- case STATE_EATING_SPACES:
190
- return true ;
191
- case STATE_ARGUMENT:
192
- args.push_back (curarg);
193
- return true ;
194
- default : // ERROR to end in one of the other states
195
- return false ;
293
+ case STATE_COMMAND_EXECUTED:
294
+ if (lastResult.isStr ())
295
+ strResult = lastResult.get_str ();
296
+ else
297
+ strResult = lastResult.write (2 );
298
+ case STATE_ARGUMENT:
299
+ case STATE_EATING_SPACES:
300
+ return true ;
301
+ default : // ERROR to end in one of the other states
302
+ return false ;
196
303
}
197
304
}
198
305
199
306
void RPCExecutor::request (const QString &command)
200
307
{
201
- std::vector<std::string> args;
202
- if (!parseCommandLine (args, command.toStdString ()))
203
- {
204
- Q_EMIT reply (RPCConsole::CMD_ERROR, QString (" Parse error: unbalanced ' or \" " ));
205
- return ;
206
- }
207
- if (args.empty ())
208
- return ; // Nothing to do
209
308
try
210
309
{
211
- std::string strPrint;
212
- // Convert argument list to JSON objects in method-dependent way,
213
- // and pass it along with the method name to the dispatcher.
214
- UniValue result = tableRPC.execute (
215
- args[0 ],
216
- RPCConvertValues (args[0 ], std::vector<std::string>(args.begin () + 1 , args.end ())));
217
-
218
- // Format result reply
219
- if (result.isNull ())
220
- strPrint = " " ;
221
- else if (result.isStr ())
222
- strPrint = result.get_str ();
223
- else
224
- strPrint = result.write (2 );
225
-
226
- Q_EMIT reply (RPCConsole::CMD_REPLY, QString::fromStdString (strPrint));
310
+ std::string result;
311
+ std::string executableCommand = command.toStdString () + " \n " ;
312
+ if (!RPCConsole::RPCExecuteCommandLine (result, executableCommand))
313
+ {
314
+ Q_EMIT reply (RPCConsole::CMD_ERROR, QString (" Parse error: unbalanced ' or \" " ));
315
+ return ;
316
+ }
317
+ Q_EMIT reply (RPCConsole::CMD_REPLY, QString::fromStdString (result));
227
318
}
228
319
catch (UniValue& objError)
229
320
{
0 commit comments