Torc  0.1
torcrpcrequest.cpp
Go to the documentation of this file.
1 /* Class TorcRPCRequest
2 *
3 * Copyright (C) Mark Kendall 2013-18
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 
20 // Qt
21 #include <QJsonArray>
22 #include <QJsonDocument>
23 
24 // Torc
25 #include "torclogging.h"
26 #include "http/torchttpserver.h"
27 #include "torcrpcrequest.h"
28 
47 TorcRPCRequest::TorcRPCRequest(const QString &Method, QObject *Parent)
48  : m_authenticated(false),
49  m_notification(false),
50  m_state(None),
51  m_id(-1),
52  m_method(Method),
53  m_parent(nullptr),
54  m_parentLock(new QMutex()),
55  m_validParent(true),
56  m_parameters(),
57  m_positionalParameters(),
58  m_serialisedData(),
59  m_reply()
60 {
61  SetParent(Parent);
62 }
63 
68 TorcRPCRequest::TorcRPCRequest(const QString &Method)
69  : m_authenticated(false),
70  m_notification(true),
71  m_state(None),
72  m_id(-1),
73  m_method(Method),
74  m_parent(nullptr),
75  m_parentLock(new QMutex()),
76  m_validParent(false),
77  m_parameters(),
78  m_positionalParameters(),
79  m_serialisedData(),
80  m_reply()
81 {
82 }
83 
86 TorcRPCRequest::TorcRPCRequest(const QJsonObject &Object, QObject *Parent, bool Authenticated)
87  : m_authenticated(Authenticated),
88  m_notification(true),
89  m_state(None),
90  m_id(-1),
91  m_method(),
92  m_parent(Parent),
93  m_parentLock(new QMutex()),
94  m_validParent(false),
95  m_parameters(),
96  m_positionalParameters(),
97  m_serialisedData(),
98  m_reply()
99 {
100  ParseJSONObject(Object);
101 }
102 
106 TorcRPCRequest::TorcRPCRequest(TorcWebSocketReader::WSSubProtocol Protocol, const QByteArray &Data, QObject *Parent, bool Authenticated)
107  : m_authenticated(Authenticated),
108  m_notification(true),
109  m_state(None),
110  m_id(-1),
111  m_method(),
112  m_parent(Parent),
113  m_parentLock(new QMutex()),
114  m_validParent(false),
115  m_parameters(),
116  m_positionalParameters(),
117  m_serialisedData(),
118  m_reply()
119 {
121  {
122  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown websocket subprotocol"));
123  return;
124  }
125 
126  // parse the JSON
127  QJsonDocument doc = QJsonDocument::fromJson(Data);
128 
129  if (doc.isNull())
130  {
131  ProcessNullContent(Data.contains("method"));
132  return;
133  }
134 
135  LOG(VB_GENERAL, LOG_DEBUG, QString(Data));
136 
137  // single request, one JSON object
138  if (doc.isObject())
139  {
140  ParseJSONObject(doc.object());
141  return;
142  }
143  // batch call
144  else if (doc.isArray())
145  {
146  ProcessBatchCall(doc.array());
147  return;
148  }
149 }
150 
151 TorcRPCRequest::~TorcRPCRequest()
152 {
153  delete m_parentLock;
154 }
155 
156 void TorcRPCRequest::ProcessBatchCall(const QJsonArray &Array)
157 {
158  QJsonObject error;
159  QJsonObject object;
160  object.insert(QStringLiteral("code"), -32600);
161  object.insert(QStringLiteral("message"), QStringLiteral("Invalid request"));
162  error.insert(QStringLiteral("error"), object);
163  error.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0"));
164  error.insert(QStringLiteral("id"), QJsonValue());
165 
166  // an empty array is an error
167  if (Array.isEmpty())
168  {
169  m_serialisedData = QJsonDocument(error).toJson();
170  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid request - empty array"));
171  return;
172  }
173 
174  // iterate over each member
175  QByteArray result;
176  result.append("[\r\n");
177  bool empty = true;
178 
179  QJsonArray::const_iterator it = Array.constBegin();
180  for ( ; it != Array.constEnd(); ++it)
181  {
182  // must be an object
183  if (!(*it).isObject())
184  {
185  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid request - not an object"));
186  result.append(QJsonDocument(error).toJson());
187  continue;
188  }
189 
190  // process this object
191  TorcRPCRequest *request = new TorcRPCRequest((*it).toObject(), m_parent, m_authenticated);
192 
193  if (!request->GetData().isEmpty())
194  {
195  if (empty)
196  empty = false;
197  else
198  result.append(",\r\n");
199  result.append(request->GetData());
200  }
201 
202  request->DownRef();
203  }
204 
205  result.append("\r\n]");
206 
207  // don't return an empty array - which would/should be a group of notifications...
208  if (!empty)
209  m_serialisedData = result;
210 }
211 
212 void TorcRPCRequest::ProcessNullContent(bool HasMethod)
213 {
214  // NB we are acting as both client and server, hence if we receive invalid JSON (that Qt cannot parse)
215  // we can only make a best efforts guess as to whether this was a request. Hence under
216  // certain circumstances, we may not send the appropriate error message or respond at all.
217  if (HasMethod)
218  {
219  QJsonObject object;
220  QJsonObject error;
221  error.insert(QStringLiteral("code"), -32700);
222  error.insert(QStringLiteral("message"), QStringLiteral("Parse error"));
223  object.insert(QStringLiteral("error"), error);
224  object.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0"));
225  object.insert(QStringLiteral("id"), QJsonValue());
226  m_serialisedData = QJsonDocument(object).toJson();
227  LOG(VB_GENERAL, LOG_INFO, QString(m_serialisedData));
228  }
229 
230  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error parsing JSON-RPC data"));
231  AddState(Errored);
232 }
233 
234 void TorcRPCRequest::ParseJSONObject(const QJsonObject &Object)
235 {
236  // determine whether this is a request or response
237  int id = (Object.contains(QStringLiteral("id")) && !Object[QStringLiteral("id")].isNull()) ? (int)Object[QStringLiteral("id")].toDouble() : -1;
238  bool isrequest = Object.contains(QStringLiteral("method"));
239  bool isresult = Object.contains(QStringLiteral("result"));
240  bool iserror = Object.contains(QStringLiteral("error"));
241 
242  if ((int)isrequest + (int)isresult + (int)iserror != 1)
243  {
244  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Ambiguous RPC request/response"));
245  AddState(Errored);
246  return;
247  }
248 
249  if (isrequest)
250  {
251  QString method = Object[QStringLiteral("method")].toString();
252  // if this is a notification, check first whether it is a subscription 'event' that the parent is monitoring
253  bool handled = false;
254  if (id < 0)
255  {
256  if (!QMetaObject::invokeMethod(m_parent, "HandleNotification", Qt::DirectConnection,
257  Q_RETURN_ARG(bool, handled), Q_ARG(QString, method)))
258  {
259  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to invoke 'HandleNotification' in request parent"));
260  }
261  }
262 
263  if (!handled)
264  {
265  QVariantMap result = TorcHTTPServer::HandleRequest(method, Object.value(QStringLiteral("params")).toVariant(), m_parent, m_authenticated);
266 
267  // not a notification, response expected
268  if (id > -1)
269  {
270  // result should contain either 'result' or 'error', we need to insert id and protocol identifier
271  result.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0"));
272  result.insert(QStringLiteral("id"), id);
273  m_serialisedData = QJsonDocument::fromVariant(result).toJson();
274  }
275  else if (Object.contains(QStringLiteral("id")))
276  {
277  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Request contains invalid id"));
278  }
279  }
280  }
281  else if (isresult)
282  {
283  m_reply = Object.value(QStringLiteral("result")).toVariant();
284  AddState(Result);
285  m_id = id;
286 
287  if (m_id < 0)
288  {
289  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Received result with no id"));
290  AddState(Errored);
291  }
292  }
293  else if (iserror)
294  {
295  AddState(Errored);
296  AddState(Result);
297  m_id = id;
298 
299  if (m_id < 0)
300  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Received error with no id"));
301  else
302  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("JSON-RPC error"));
303  }
304 }
305 
307 {
308  return m_notification;
309 }
310 
313 {
314  QMutexLocker locker(m_parentLock);
315 
316  if (!m_parent || !m_validParent || m_state & Cancelled)
317  return;
318 
319  QMetaObject::invokeMethod(m_parent, "RequestReady", Q_ARG(TorcRPCRequest*, this));
320 }
321 
327 void TorcRPCRequest::SetParent(QObject *Parent)
328 {
329  QMutexLocker locker(m_parentLock);
330 
331  m_parent = Parent;
332  m_validParent = m_parent != nullptr;
333 
334  if (m_parent && m_parent->metaObject()->indexOfMethod(QMetaObject::normalizedSignature("RequestReady(TorcRPCRequest*)")) < 0)
335  {
336  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Request's parent does not have RequestReady method - request WILL fail"));
337  m_validParent = false;
338  }
339 }
340 
346 {
348  {
349  QJsonObject object;
350  object.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0"));
351  object.insert(QStringLiteral("method"), m_method);
352 
353  // named paramaters are preferred over positional
354  if (!m_parameters.isEmpty())
355  {
356  QJsonObject params;
357  for (int i = 0; i < m_parameters.size(); ++i)
358  params.insert(m_parameters[i].first, QJsonValue::fromVariant(m_parameters[i].second));
359  object.insert(QStringLiteral("params"), params);
360  }
361  else if (!m_positionalParameters.isEmpty())
362  {
363  // NB positional parameters are serialised as an array (ordered)
364  QJsonArray params;
365  for (int i = 0; i < m_positionalParameters.size(); ++i)
366  params.append(QJsonValue::fromVariant(m_positionalParameters[i]));
367  object.insert(QStringLiteral("params"), params);
368  }
369 
370  if (m_id > -1)
371  object.insert(QStringLiteral("id"), m_id);
372 
373  QJsonDocument doc(object);
374  m_serialisedData = doc.toJson();
375  }
376 
377  LOG(VB_NETWORK, LOG_DEBUG, QString(m_serialisedData));
378 
379  return m_serialisedData;
380 }
381 
387 {
388  m_state |= State;
389 }
390 
393 {
394  m_id = ID;
395 }
396 
402 void TorcRPCRequest::AddParameter(const QString &Name, const QVariant &Value)
403 {
404  m_parameters.append(QPair<QString,QVariant>(Name, Value));
405 }
406 
412 void TorcRPCRequest::AddPositionalParameter(const QVariant &Value)
413 {
414  m_positionalParameters.append(Value);
415 }
416 
417 void TorcRPCRequest::SetReply(const QVariant &Reply)
418 {
419  m_reply = Reply;
420 }
421 
423 {
424  return m_state;
425 }
426 
427 int TorcRPCRequest::GetID(void) const
428 {
429  return m_id;
430 }
431 
432 QString TorcRPCRequest::GetMethod(void) const
433 {
434  return m_method;
435 }
436 
437 QObject* TorcRPCRequest::GetParent(void) const
438 {
439  return m_parent;
440 }
441 
442 const QVariant& TorcRPCRequest::GetReply(void) const
443 {
444  return m_reply;
445 }
446 
447 const QList<QPair<QString,QVariant> >& TorcRPCRequest::GetParameters(void) const
448 {
449  return m_parameters;
450 }
451 
452 const QList<QVariant>& TorcRPCRequest::GetPositionalParameters(void) const
453 {
454  return m_positionalParameters;
455 }
456 
457 QByteArray& TorcRPCRequest::GetData(void)
458 {
459  return m_serialisedData;
460 }
TorcRPCRequest(const QString &Method, QObject *Parent)
Creates an RPC request owned by the given Parent.
QByteArray & GetData(void)
int GetID(void) const
static void HandleRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request)
void SetParent(QObject *Parent)
Set the parent for the request.
virtual bool DownRef(void)
void AddState(int State)
Progress the state for this request.
A class encapsulating a Remote Procedure Call.
void NotifyParent(void)
Signal to the parent that the request is ready (but may be errored).
QString GetMethod(void) const
VERBOSE_PREAMBLE true
int GetState(void) const
void AddPositionalParameter(const QVariant &Value)
Add a positional parameter.
const QList< QVariant > & GetPositionalParameters(void) const
bool IsNotification(void) const
QByteArray & SerialiseRequest(TorcWebSocketReader::WSSubProtocol Protocol)
Serialise the request for the given protocol.
const QList< QPair< QString, QVariant > > & GetParameters(void) const
const QVariant & GetReply(void) const
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
void SetReply(const QVariant &Reply)
QObject * GetParent(void) const
void AddParameter(const QString &Name, const QVariant &Value)
Add Parameter and value to list of call parameters.
void SetID(int ID)
Set the unique ID for this request.