Torc  0.1
torchttpservice.cpp
Go to the documentation of this file.
1 /* Class TorcHTTPService
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2012-18
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 // Qt
24 #include <QObject>
25 #include <QMetaType>
26 #include <QMetaMethod>
27 #include <QTime>
28 #include <QDate>
29 #include <QDateTime>
30 
31 // Torc
32 #include "torclocaldefs.h"
33 #include "torclogging.h"
34 #include "torcnetwork.h"
35 #include "torchttpserver.h"
36 #include "torcjsonrpc.h"
37 #include "torcserialiser.h"
38 #include "torchttpservice.h"
39 #include "torcexitcodes.h"
40 
42 {
43  public:
44  MethodParameters(int Index, QMetaMethod Method, int AllowedRequestTypes, const QString &ReturnType)
45  : m_valid(false),
46  m_index(Index),
47  m_names(),
48  m_types(),
49  m_allowedRequestTypes(AllowedRequestTypes),
50  m_returnType(ReturnType),
51  m_method(Method)
52  {
53  // statically initialise the list of unsupported types (either non-serialisable (QHash)
54  // or nonsensical (pointer types)
55  static QList<int> unsupportedtypes;
56  static QList<int> unsupportedparameters;
57  static bool initialised = false;
58 
59  if (!initialised)
60  {
61  // this list is probably incomplete
62  initialised = true;
63  unsupportedtypes << QMetaType::UnknownType;
64  unsupportedtypes << QMetaType::VoidStar << QMetaType::QObjectStar << QMetaType::QVariantHash;
65  unsupportedtypes << QMetaType::QRect << QMetaType::QRectF << QMetaType::QSize << QMetaType::QSizeF << QMetaType::QLine << QMetaType::QLineF << QMetaType::QPoint << QMetaType::QPointF;
66 
67  unsupportedparameters << unsupportedtypes;
68  unsupportedparameters << QMetaType::QVariantMap << QMetaType::QStringList << QMetaType::QVariantList;
69  }
70 
71  // the return type/value is first
72  int returntype = QMetaType::type(Method.typeName());
73 
74  // discard slots with an unsupported return type
75  if (unsupportedtypes.contains(returntype))
76  {
77  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Method '%1' has unsupported return type '%2'").arg(Method.name().constData(), Method.typeName()));
78  return;
79  }
80 
81  // discard overly complicated slots not supported by QMetaMethod
82  if (Method.parameterCount() > 10)
83  {
84  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Method '%1' takes more than 10 parameters - ignoring").arg(Method.name().constData()));
85  return;
86  }
87 
88  m_types.append(returntype > 0 ? returntype : 0);
89  m_names.append(Method.name());
90 
91  QList<QByteArray> names = Method.parameterNames();
92  QList<QByteArray> types = Method.parameterTypes();
93 
94  // add type/value for each method parameter
95  for (int i = 0; i < names.size(); ++i)
96  {
97  int type = QMetaType::type(types[i]);
98 
99  // discard slots that use unsupported parameter types
100  if (unsupportedparameters.contains(type))
101  {
102  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Method '%1' has unsupported parameter type '%2'")
103  .arg(Method.name().constData(), types[i].constData()));
104  return;
105  }
106 
107  m_names.append(names[i]);
108  m_types.append(type);
109  }
110 
111  m_valid = true;
112  }
113 
114  ~MethodParameters() = default;
115 
120  QVariant Invoke(QObject *Object, const QMap<QString,QString> &Queries, QString &ReturnType, bool &VoidResult)
121  {
122  // this may be called by multiple threads simultaneously, so we need to create our own paramaters instance.
123  // N.B. QMetaObject::invokeMethod only supports up to 10 arguments (plus a return value)
124  void* parameters[11];
125  memset(parameters, 0, 11 * sizeof(void*));
126  int size = qMin(11, m_types.size());
127 
128  // check parameter count
129  if (Queries.size() != size - 1)
130  {
131  if (!m_names.isEmpty())
132  {
133  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Method '%1' expects %2 parameters, sent %3")
134  .arg(m_names.value(0).constData()).arg(size - 1).arg(Queries.size()));
135  }
136  return QVariant();
137  }
138 
139  // populate parameters from query and ensure each parameter is listed
140  QMap<QString,QString>::const_iterator it;
141  for (int i = 0; i < size; ++i)
142  {
143  parameters[i] = QMetaType::create(m_types.value(i));
144  if (i)
145  {
146  it = Queries.constFind(m_names.value(i));
147  if (it == Queries.end())
148  {
149  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Parameter '%1' for method '%2' is missing")
150  .arg(m_names.value(i).constData(), m_names.value(0).constData()));
151  return QVariant();
152  }
153  SetValue(parameters[i], it.value(), m_types.value(i));
154  }
155  }
156 
157  if (Object->qt_metacall(QMetaObject::InvokeMetaMethod, m_index, parameters) > -1)
158  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("qt_metacall error"));
159 
160  // handle QVariant return type where we don't want to lose visibility of the underlying type
161  int type = m_types.value(0);
162  if (type == QMetaType::QVariant)
163  {
164  int newtype = static_cast<int>(reinterpret_cast<QVariant*>(parameters[0])->type());
165  if (newtype != type)
166  type = newtype;
167  }
168 
169  // we cannot create a QVariant that is void and an invalid QVariant signals an error state,
170  // so flag directly
171  VoidResult = type == QMetaType::Void;
172  QVariant result = type == QMetaType::Void ? QVariant() : QVariant(type, parameters[0]);
173 
174  // free allocated parameters
175  for (int i = 0; i < size; ++i)
176  if (parameters[i])
177  QMetaType::destroy(m_types.at(i), parameters[i]);
178 
179  ReturnType = m_returnType;
180  return result;
181  }
182 
183  static void SetValue(void* Pointer, const QString &Value, int Type)
184  {
185  if (!Pointer)
186  return;
187 
188  switch (Type)
189  {
190  case QMetaType::QVariant: *((QVariant*)Pointer) = QVariant(Value); return;
191  case QMetaType::Char: *((char*)Pointer) = Value.isEmpty() ? 0 : Value.at(0).toLatin1(); return;
192  case QMetaType::UChar: *((unsigned char*)Pointer) = Value.isEmpty() ? 0 :Value.at(0).toLatin1(); return;
193  case QMetaType::QChar: *((QChar*)Pointer) = Value.isEmpty() ? 0 : Value.at(0); return;
194  case QMetaType::Bool: *((bool*)Pointer) = ToBool(Value); return;
195  case QMetaType::Short: *((short*)Pointer) = Value.toShort(); return;
196  case QMetaType::UShort: *((ushort*)Pointer) = Value.toUShort(); return;
197  case QMetaType::Int: *((int*)Pointer) = Value.toInt(); return;
198  case QMetaType::UInt: *((uint*)Pointer) = Value.toUInt(); return;
199  case QMetaType::Long: *((long*)Pointer) = Value.toLong(); return;
200  case QMetaType::ULong: *((ulong*)Pointer) = Value.toULong(); return;
201  case QMetaType::LongLong: *((qlonglong*)Pointer) = Value.toLongLong(); return;
202  case QMetaType::ULongLong: *((qulonglong*)Pointer) = Value.toULongLong(); return;
203  case QMetaType::Double: *((double*)Pointer) = Value.toDouble(); return;
204  case QMetaType::Float: *((float*)Pointer) = Value.toFloat(); return;
205  case QMetaType::QString: *((QString*)Pointer) = Value; return;
206  case QMetaType::QByteArray: *((QByteArray*)Pointer) = Value.toUtf8(); return;
207  case QMetaType::QTime: *((QTime*)Pointer) = QTime::fromString(Value, Qt::ISODate); return;
208  case QMetaType::QDate: *((QDate*)Pointer) = QDate::fromString(Value, Qt::ISODate); return;
209  case QMetaType::QDateTime:
210  {
211  QDateTime dt = QDateTime::fromString(Value, Qt::ISODate);
212  dt.setTimeSpec(Qt::UTC);
213  *((QDateTime*)Pointer) = dt;
214  return;
215  }
216  default: break;
217  }
218  }
219 
220  static bool ToBool(const QString &Value)
221  {
222  if (Value.compare(QStringLiteral("1"), Qt::CaseInsensitive) == 0)
223  return true;
224  if (Value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0)
225  return true;
226  if (Value.compare(QStringLiteral("y"), Qt::CaseInsensitive) == 0)
227  return true;
228  if (Value.compare(QStringLiteral("yes"), Qt::CaseInsensitive) == 0)
229  return true;
230  return false;
231  }
232 
235  void Disable(void)
236  {
238  }
239 
242  void Enable(void)
243  {
246  }
247 
248  bool m_valid;
249  int m_index;
250  QVector<QByteArray> m_names;
251  QVector<int> m_types;
253  QString m_returnType;
254  QMetaMethod m_method;
255 };
256 
262 TorcHTTPService::TorcHTTPService(QObject *Parent, const QString &Signature, const QString &Name,
263  const QMetaObject &MetaObject, const QString &Blacklist)
264  : TorcHTTPHandler(TORC_SERVICES_DIR + Signature, Name),
265  m_httpServiceLock(QReadWriteLock::Recursive),
266  m_parent(Parent),
267  m_version(QStringLiteral("Unknown")),
268  m_methods(),
269  m_properties(),
270  m_subscribers(),
271  m_subscriberLock(QMutex::Recursive)
272 {
273  static const QString defaultblacklisted(QStringLiteral("deleteLater,SubscriberDeleted,"));
274  QStringList blacklist = (defaultblacklisted + Blacklist).split(',');
275 
276  m_parent->setObjectName(Name);
277 
278  // the parent MUST implement SubscriberDeleted.
279  if (MetaObject.indexOfSlot(QMetaObject::normalizedSignature("SubscriberDeleted(QObject*)")) < 0)
280  {
281  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Service '%1' has no SubscriberDeleted slot. This is a programmer error - exiting").arg(Name));
282  QCoreApplication::exit(TORC_EXIT_UNKOWN_ERROR);
283  return;
284  }
285 
286  // determine version
287  int index = MetaObject.indexOfClassInfo("Version");
288  if (index > -1)
289  m_version = MetaObject.classInfo(index).value();
290  else
291  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Service '%1' is missing version information").arg(Name));
292 
293  // is this a secure service (all methods require authentication)
294  bool secure = MetaObject.indexOfClassInfo("Secure") > -1;
295 
296  // build a list of metaobjects from all superclasses as well.
297  QList<const QMetaObject*> metas;
298  metas.append(&MetaObject);
299  const QMetaObject* super = MetaObject.superClass();
300  while (super)
301  {
302  metas.append(super);
303  super = super->superClass();
304  }
305 
306  // analyse available methods. Build the list from the top superclass 'down' to ensure we pick up
307  // overriden slots and discard duplicates
308  QListIterator<const QMetaObject*> it(metas);
309  it.toBack();
310  while (it.hasPrevious())
311  {
312  const QMetaObject *meta = it.previous();
313 
314  for (int i = 0; i < meta->methodCount(); ++i)
315  {
316  QMetaMethod method = meta->method(i);
317 
318  if ((method.methodType() == QMetaMethod::Slot) &&
319  (method.access() == QMetaMethod::Public))
320  {
321  QString name(method.methodSignature());
322  name = name.section('(', 0, 0);
323 
324  // discard unwanted slots
325  if (blacklist.contains(name))
326  continue;
327 
328  // any Q_CLASSINFO for this method?
329  // current 'schema' allows specification of allowed HTTP methods (PUT, GET etc),
330  // custom return types, which are used to improve the usability of maps and
331  // lists when returned via XML, JSON, PLIST etc and requiring authentication (add AUTH to methods)
332  QString returntype;
333  int customallowed = HTTPUnknownType;
334 
335  // use the actual class metaObject - not the superclass
336  int index = MetaObject.indexOfClassInfo(name.toLatin1());
337  if (index > -1)
338  {
339  QStringList infos = QString(MetaObject.classInfo(index).value()).split(',', QString::SkipEmptyParts);
340  foreach (const QString &info, infos)
341  {
342  if (info.startsWith(QStringLiteral("methods=")))
343  customallowed = TorcHTTPRequest::StringToAllowed(info.mid(8));
344  else if (info.startsWith(QStringLiteral("type=")))
345  returntype = info.mid(5);
346  }
347  }
348 
349  // determine allowed request types
350  int allowed = HTTPOptions;
351  if (secure)
352  allowed |= HTTPAuth;
353  if (customallowed != HTTPUnknownType)
354  {
355  allowed |= customallowed;
356  }
357  else if (name.startsWith(QStringLiteral("Get"), Qt::CaseInsensitive))
358  {
359  allowed |= HTTPGet | HTTPHead;
360  }
361  else if (name.startsWith(QStringLiteral("Set"), Qt::CaseInsensitive))
362  {
363  // TODO Put or Post?? How to handle head requests for setters...
364  allowed |= HTTPPut;
365  }
366  else
367  {
368  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unable to determine request types of method '%1' for '%2' - ignoring").arg(name, m_name));
369  continue;
370  }
371 
372  if (allowed & HTTPAuth)
373  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("%1 requires authentication").arg(Signature + name));
374 
375  MethodParameters *parameters = new MethodParameters(i, method, allowed, returntype);
376 
377  if (parameters->m_valid)
378  {
379  // check whether method has already been identified from superclass - need to match whole 'signature'
380  // not just name
381  if (m_methods.contains(name))
382  {
383  MethodParameters *existing = m_methods.value(name);
384  if (existing->m_method.methodSignature() == method.methodSignature() &&
385  existing->m_method.returnType() == method.returnType())
386  {
387  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Method '%1' in class '%2' already found in superclass - overriding")
388  .arg(method.methodSignature().constData(), meta->className()));
389  existing = m_methods.take(name);
390  delete existing;
391  }
392  }
393 
394  m_methods.insert(name, parameters);
395  }
396  else
397  {
398  delete parameters;
399  }
400  }
401  }
402  }
403 
404  // analyse properties from the full list of metaobjects
405  int invalidindex = -1;
406  foreach (const QMetaObject* meta, metas)
407  {
408  for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i)
409  {
410  QMetaProperty property = meta->property(i);
411  QString propertyname(property.name());
412 
413  if (propertyname != QStringLiteral("objectName") && property.isReadable() && ((property.hasNotifySignal() && property.notifySignalIndex() > -1) || property.isConstant()))
414  {
415  // constant properties are given a signal index < 0
416  if (property.notifySignalIndex() > -1)
417  {
418  m_properties.insert(property.notifySignalIndex(), property.propertyIndex());
419  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Adding property '%1' with signal index %2").arg(property.name()).arg(property.notifySignalIndex()));
420  }
421  else
422  {
423  m_properties.insert(invalidindex--, property.propertyIndex());
424  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Adding constant property '%1'").arg(property.name()));
425  }
426  }
427  }
428  }
429 }
430 
432 {
433  qDeleteAll(m_methods);
434 }
435 
437 {
438  return Name();
439 }
440 
442 {
443  return QString();
444 }
445 
446 void TorcHTTPService::ProcessHTTPRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request)
447 {
448  (void)PeerAddress;
449  (void)PeerPort;
450  (void)LocalAddress;
451  (void)LocalPort;
452 
453  QString method = Request.GetMethod();
454  HTTPRequestType type = Request.GetHTTPRequestType();
455 
456  if (method.compare(QStringLiteral("GetServiceVersion")) == 0)
457  {
458  if (type == HTTPOptions)
459  {
460  Request.SetStatus(HTTP_OK);
462  Request.SetAllowed(HTTPHead | HTTPGet | HTTPOptions);
463  return;
464  }
465 
466  if (type != HTTPGet && type != HTTPHead)
467  {
468  Request.SetStatus(HTTP_BadRequest);
470  return;
471  }
472 
473  Request.SetStatus(HTTP_OK);
474  Request.Serialise(m_version, TORC_SERVICE_VERSION);
475  return;
476  }
477 
478  QMap<QString,MethodParameters*>::const_iterator it = m_methods.constFind(method);
479  if (it != m_methods.constEnd())
480  {
481  // filter out invalid request types
482  if ((!(type & (*it)->m_allowedRequestTypes)) ||
483  (*it)->m_allowedRequestTypes & HTTPDisabled)
484  {
485  Request.SetStatus(HTTP_BadRequest);
487  return;
488  }
489 
490  // handle OPTIONS
491  if (type == HTTPOptions)
492  {
493  HandleOptions(Request, (*it)->m_allowedRequestTypes);
494  return;
495  }
496 
497  // reject requests that have a particular need for authorisation. We can only
498  // check at this late stage but these should be in the minority
499  if (!MethodIsAuthorised(Request, (*it)->m_allowedRequestTypes))
500  return;
501 
502  QString type;
503  bool voidresult;
504  QVariant result = (*it)->Invoke(m_parent, Request.Queries(), type, voidresult);
505 
506  // is there a result
507  if (!voidresult)
508  {
509  // check for invocation errors
510  if (result.type() == QVariant::Invalid)
511  {
512  Request.SetStatus(HTTP_BadRequest);
514  return;
515  }
516 
517  Request.Serialise(result, type);
518  Request.SetAllowGZip(true);
519  }
520  else
521  {
523  }
524 
525  Request.SetStatus(HTTP_OK);
526  }
527 }
528 
529 QVariantMap TorcHTTPService::ProcessRequest(const QString &Method, const QVariant &Parameters, QObject *Connection, bool Authenticated)
530 {
531  QString method;
532  int index = Method.lastIndexOf('/');
533  if (index > -1)
534  method = Method.mid(index + 1).trimmed();
535 
536  if (Connection && !method.isEmpty())
537  {
538  // find the correct method to invoke
539  QMap<QString,MethodParameters*>::const_iterator it = m_methods.constFind(method);
540  if (it != m_methods.constEnd())
541  {
542  // disallow methods based on state and authentication
543  int types = it.value()->m_allowedRequestTypes;
544  bool disabled = types & HTTPDisabled;
545  bool unauthorised = !Authenticated && (types & HTTPAuth || types & HTTPPost || types & HTTPPut || types & HTTPDelete || types & HTTPUnknownType);
546 
547  if (disabled || unauthorised)
548  {
549  if (disabled)
550  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("'%1' method '%2' is disabled").arg(m_signature, method));
551  else
552  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("'%1' method '%2' unauthorised").arg(m_signature, method));
553  QVariantMap result;
554  QVariantMap error;
555  error.insert(QStringLiteral("code"), -401); // HTTP 401!
556  error.insert(QStringLiteral("message"), QStringLiteral("Method not authorised"));
557  result.insert(QStringLiteral("error"), error);
558  return result;
559  }
560  else
561  {
562  // invoke it
563 
564  // convert the parameters
565  QMap<QString,QString> params;
566  if (Parameters.type() == QVariant::Map)
567  {
568  QVariantMap map = Parameters.toMap();
569  QVariantMap::iterator it = map.begin();
570  for ( ; it != map.end(); ++it)
571  params.insert(it.key(), it.value().toString());
572  }
573  else if (Parameters.type() == QVariant::List)
574  {
575  QVariantList list = Parameters.toList();
576  if (list.size() <= (*it)->m_names.size())
577  {
578  for (int i = 0; i < list.size(); ++i)
579  params.insert((*it)->m_names[i], list[i].toString());
580  }
581  else
582  {
583  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Too many parameters"));
584  }
585  }
586  else if (!Parameters.isNull())
587  {
588  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown parameter variant"));
589  }
590 
591  QString type;
592  bool voidresult = false;
593  QVariant results = (*it)->Invoke(m_parent, params, type, voidresult);
594 
595  // check result
596  if (!voidresult)
597  {
598  // check for invocation errors
599  if (results.type() != QVariant::Invalid)
600  {
601  QVariantMap result;
602  result.insert(QStringLiteral("result"), results);
603  return result;
604  }
605 
606  QVariantMap result;
607  QVariantMap error;
608  error.insert(QStringLiteral("code"), -32602);
609  error.insert(QStringLiteral("message"), QStringLiteral("Invalid params"));
610  result.insert(QStringLiteral("error"), error);
611  return result;
612  }
613 
614  // JSON-RPC 2.0 specification makes no mention of void/null return types
615  QVariantMap result;
616  result.insert(QStringLiteral("result"), QStringLiteral("null"));
617  return result;
618  }
619  }
620 
621  // implicit 'GetServiceVersion' method
622  if (method.compare(QStringLiteral("GetServiceVersion")) == 0)
623  {
624  QVariantMap result;
625  QVariantMap version;
626  version.insert(TORC_SERVICE_VERSION, m_version);
627  result.insert(QStringLiteral("result"), version);
628  return result;
629  }
630  // implicit 'Subscribe' method
631  else if (method.compare(QStringLiteral("Subscribe")) == 0)
632  {
633  // ensure the 'receiver' has all of the right slots
634  int change = Connection->metaObject()->indexOfSlot(QMetaObject::normalizedSignature("PropertyChanged()"));
635  if (change > -1)
636  {
637  // this method is not thread-safe and is called from multiple threads so lock the subscribers
638  QMutexLocker locker(&m_subscriberLock);
639 
640  if (!m_subscribers.contains(Connection))
641  {
642  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("New subscription for '%1'").arg(m_signature));
643  m_subscribers.append(Connection);
644 
645  // notify success and provide appropriate details about properties, notifications, get'ers etc
646  QMap<int,int>::const_iterator it = m_properties.constBegin();
647  for ( ; it != m_properties.constEnd(); ++it)
648  {
649  // and connect property change notifications to the one slot
650  // NB we use the parent's metaObject here - not the staticMetaObject (or m_metaObject)
651  if (it.key() > -1)
652  QObject::connect(m_parent, m_parent->metaObject()->method(it.key()), Connection, Connection->metaObject()->method(change));
653 
654  // clean up subscriptions if the subscriber is deleted
655  QObject::connect(Connection, SIGNAL(destroyed(QObject*)), m_parent, SLOT(SubscriberDeleted(QObject*)));// clazy:exclude=old-style-connect
656  }
657 
658  QVariantMap result;
659  result.insert(QStringLiteral("result"), GetServiceDetails());
660  return result;
661  }
662  else
663  {
664  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Connection is already subscribed to '%1'").arg(m_signature));
665  }
666  }
667  else
668  {
669  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Subscription request for connection without correct methods"));
670  }
671 
672  QVariantMap result;
673  QVariantMap error;
674  error.insert(QStringLiteral("code"), -32603);
675  error.insert(QStringLiteral("message"), "Internal error");
676  result.insert(QStringLiteral("error"), error);
677  return result;
678  }
679  // implicit 'Unsubscribe' method
680  else if (method.compare(QStringLiteral("Unsubscribe")) == 0)
681  {
682  QMutexLocker locker(&m_subscriberLock);
683 
684  if (m_subscribers.contains(Connection))
685  {
686  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Removed subscription for '%1'").arg(m_signature));
687 
688  // disconnect all change signals
689  m_parent->disconnect(Connection);
690 
691  // remove the subscriber
692  m_subscribers.removeAll(Connection);
693 
694  // return success
695  QVariantMap result;
696  result.insert(QStringLiteral("result"), 1);
697  return result;
698  }
699 
700  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Connection is not subscribed to '%1'").arg(m_signature));
701 
702  QVariantMap result;
703  QVariantMap error;
704  error.insert(QStringLiteral("code"), -32603);
705  error.insert(QStringLiteral("message"), "Internal error");
706  result.insert(QStringLiteral("error"), error);
707  return result;
708  }
709  }
710 
711  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("'%1' service has no '%2' method").arg(m_signature, method));
712 
713  QVariantMap result;
714  QVariantMap error;
715  error.insert(QStringLiteral("code"), -32601);
716  error.insert(QStringLiteral("message"), QStringLiteral("Method not found"));
717  result.insert(QStringLiteral("error"), error);
718  return result;
719 }
720 
730 {
731  // no need for locking here
732 
733  QVariantMap details;
734  QVariantMap properties;
735  QVariantMap methods;
736 
737  QMap<int,int>::const_iterator it = m_properties.constBegin();
738  for ( ; it != m_properties.constEnd(); ++it)
739  {
740  // NB for some reason, QMetaProperty doesn't provide the QMetaMethod for the read and write
741  // slots, so try to infer them (and check the result)
742  QMetaProperty property = m_parent->metaObject()->property(it.value());
743  QVariantMap description;
744  QString name = QString::fromLatin1(property.name());
745  if (name.isEmpty())
746  continue;
747 
748  description.insert(QStringLiteral("notification"), QString::fromLatin1(m_parent->metaObject()->method(it.key()).name()));
749 
750  // a property is always readable
751  QString camelname = name.at(0).toUpper() + name.mid(1);
752  QString read = QString(QStringLiteral("Get")) + camelname;
753 
754  if (m_parent->metaObject()->indexOfSlot(QMetaObject::normalizedSignature(QString(read + "()").toLatin1())) > -1)
755  description.insert(QStringLiteral("read"), read);
756  else
757  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to deduce 'read' slot for property '%1' in service '%2'").arg(name, m_signature));
758 
759  // for writable properties, we need to infer the signature including the type
760  if (property.isWritable())
761  {
762  QString write = QString(QStringLiteral("Set")) + camelname;
763  if (m_parent->metaObject()->indexOfSlot(QMetaObject::normalizedSignature(QStringLiteral("%1(%2)").arg(write, property.typeName()).toLatin1())) > -1)
764  description.insert(QStringLiteral("write"), write);
765  else
766  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to deduce 'write' slot for property '%1' in service '%2'").arg(name, m_signature));
767  }
768 
769  // and add the initial value
770  description.insert(QStringLiteral("value"), property.read(m_parent));
771 
772  properties.insert(name, description);
773  }
774 
775  QVariantList params;
776  QMap<QString,MethodParameters*>::const_iterator it2 = m_methods.constBegin();
777  for ( ; it2 != m_methods.constEnd(); ++it2)
778  {
779  QVariantMap map;
780 
781  MethodParameters *parameters = it2.value();
782  for (int i = 1; i < parameters->m_types.size(); ++i)
783  params.append(parameters->m_names.value(i).constData());
784  map.insert(QStringLiteral("params"), params);
785  map.insert(QStringLiteral("returns"), TorcJSONRPC::QMetaTypetoJavascriptType(parameters->m_types[0]));
786  methods.insert(it2.key(), map);
787  params.clear();
788  }
789 
790  // and implicit Subscribe/Unsubscribe/GetServiceVersion
791  // NB these aren't implemented as public slots and property (for serviceVersion)
792  // as TorcHTTPService is not a QObject. Not ideal as the full API is not
793  // visible in the code.
794  params.clear();
795  QVariant returns("object");
796 
797  QVariantMap subscribe;
798  subscribe.insert(QStringLiteral("params"), params);
799  subscribe.insert(QStringLiteral("returns"), returns);
800  methods.insert(QStringLiteral("Subscribe"), subscribe);
801 
802  QVariantMap unsubscribe;
803  unsubscribe.insert(QStringLiteral("params"), params);
804  unsubscribe.insert(QStringLiteral("returns"), returns);
805  methods.insert(QStringLiteral("Unsubscribe"), unsubscribe);
806 
807  QVariantMap serviceversion;
808  serviceversion.insert(QStringLiteral("params"), params);
809  serviceversion.insert(QStringLiteral("returns"), TorcJSONRPC::QMetaTypetoJavascriptType(QMetaType::QString));
810  methods.insert(QStringLiteral("GetServiceVersion"), serviceversion);
811 
812  // and the implicit version property
813  QVariantMap description;
814  description.insert(QStringLiteral("read"), QStringLiteral("GetServiceVersion"));
815  description.insert(QStringLiteral("value"), m_version);
816  properties.insert(QStringLiteral("serviceVersion"), description);
817 
818  details.insert(QStringLiteral("properties"), properties);
819  details.insert(QStringLiteral("methods"), methods);
820  return details;
821 }
822 
823 QString TorcHTTPService::GetMethod(int Index)
824 {
825  if (Index > -1 && Index < m_parent->metaObject()->methodCount())
826  return QString::fromLatin1(m_parent->metaObject()->method(Index).name());
827 
828  return QString();
829 }
830 
835 QVariant TorcHTTPService::GetProperty(int Index)
836 {
837  QVariant result;
838 
839  if (Index > -1 && m_properties.contains(Index))
840  result = m_parent->metaObject()->property(m_properties.value(Index)).read(m_parent);
841  else
842  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to retrieve property"));
843 
844  return result;
845 }
846 
848 {
849  QMutexLocker locker(&m_subscriberLock);
850 
851  if (Subscriber && m_subscribers.contains(Subscriber))
852  {
853  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Subscriber deleted - cancelling subscription"));
854  m_subscribers.removeAll(Subscriber);
855  }
856 }
void SetAllowed(int Allowed)
A class to encapsulate an incoming HTTP request.
QVector< int > m_types
static void SetValue(void *Pointer, const QString &Value, int Type)
HTTPRequestType GetHTTPRequestType(void) const
Base HTTP response handler class.
virtual ~TorcHTTPService()
QVector< QByteArray > m_names
#define TORC_SERVICES_DIR
Definition: torclocaldefs.h:12
MethodParameters(int Index, QMetaMethod Method, int AllowedRequestTypes, const QString &ReturnType)
void Enable(void)
Enable this method.
void ProcessHTTPRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request) override
#define TORC_EXIT_UNKOWN_ERROR
Definition: torcexitcodes.h:6
virtual QString GetUIName(void)
void Serialise(const QVariant &Data, const QString &Type)
static void HandleOptions(TorcHTTPRequest &Request, int Allowed)
QString GetMethod(void) const
virtual QString GetPresentationURL(void)
QString Name(void) const
~MethodParameters()=default
void SetAllowGZip(bool Allowed)
Allow gzip compression for the contents of this request.
void Disable(void)
Disable this method.
void HandleSubscriberDeleted(QObject *Subscriber)
#define TORC_SERVICE_VERSION
static bool ToBool(const QString &Value)
QVariantMap ProcessRequest(const QString &Method, const QVariant &Parameters, QObject *Connection, bool Authenticated) override
static int StringToAllowed(const QString &Allowed)
void SetStatus(HTTPStatus Status)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
QVariant GetProperty(int Index)
Get the value of a given property.
static bool MethodIsAuthorised(TorcHTTPRequest &Request, int Allowed)
Check the current request is authorised and set the authentication header if not. ...
QString GetMethod(int Index)
HTTPRequestType
void SetResponseType(HTTPResponseType Type)
QVariantMap GetServiceDetails(void)
Return a QVariantMap describing the services methods and properties.
static QString QMetaTypetoJavascriptType(int MetaType)
Definition: torcjsonrpc.cpp:29
QVariant Invoke(QObject *Object, const QMap< QString, QString > &Queries, QString &ReturnType, bool &VoidResult)
Call the stored method with the arguments passed in via Queries.
TorcHTTPService(QObject *Parent, const QString &Signature, const QString &Name, const QMetaObject &MetaObject, const QString &Blacklist=QStringLiteral(""))
const QMap< QString, QString > & Queries(void) const