@@ -113,117 +113,208 @@ class QtRPCTimerInterface: public RPCTimerInterface
113113#include " rpcconsole.moc"
114114
115115/* *
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.
117118 *
118- * - Arguments are delimited with whitespace
119+ * - Command nesting is possible with brackets [example: validateaddress(getnewaddress())]
120+ * - Arguments are delimited with whitespace or comma
119121 * - Extra whitespace at the beginning and end and between arguments will be ignored
120122 * - Text can be "double" or 'single' quoted
121123 * - The backslash \c \ is used as escape character
122124 * - Outside quotes, any character can be escaped
123125 * - Within double quotes, only escape \c " and backslashes before a \c " or another backslash
124126 * - Within single quotes, no escaping is possible and no special interpretation takes place
125127 *
126- * @param[out] args Parsed arguments will be appended to this list
128+ * @param[out] result stringified Result from the executed command(chain)
127129 * @param[in] strCommand Command line to split
128130 */
129- bool parseCommandLine (std::vector<std::string> &args, const std::string &strCommand)
131+
132+ bool RPCConsole::RPCExecuteCommandLine (std::string &strResult, const std::string &strCommand)
130133{
134+ std::vector< std::vector<std::string> > stack;
135+ stack.push_back (std::vector<std::string>());
136+
131137 enum CmdParseState
132138 {
133139 STATE_EATING_SPACES,
134140 STATE_ARGUMENT,
135141 STATE_SINGLEQUOTED,
136142 STATE_DOUBLEQUOTED,
137143 STATE_ESCAPE_OUTER,
138- STATE_ESCAPE_DOUBLEQUOTED
144+ STATE_ESCAPE_DOUBLEQUOTED,
145+ STATE_COMMAND_EXECUTED,
146+ STATE_COMMAND_EXECUTED_INNER
139147 } state = STATE_EATING_SPACES;
140148 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)
142155 {
143156 switch (state)
144157 {
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:
148160 {
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)
154163 {
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;
157218 }
158- state = STATE_EATING_SPACES;
159- break ;
160- default : curarg += ch; state = STATE_ARGUMENT;
219+ if (breakParsing)
220+ break ;
161221 }
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)
165225 {
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;
168265 }
169- break ;
170- case STATE_DOUBLEQUOTED : // Double -quoted string
171- switch (ch)
266+ break ;
267+ case STATE_SINGLEQUOTED : // Single -quoted string
268+ switch (ch)
172269 {
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;
176272 }
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 ;
185289 }
186290 }
187291 switch (state) // final state
188292 {
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 ;
196303 }
197304}
198305
199306void RPCExecutor::request (const QString &command)
200307{
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
209308 try
210309 {
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));
227318 }
228319 catch (UniValue& objError)
229320 {
0 commit comments