Torc  0.1
torchttpserver.cpp
Go to the documentation of this file.
1 /* Class TorcHTTPServer
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 <QUrl>
25 #include <QTcpSocket>
26 #include <QCoreApplication>
27 
28 // Torc
29 #include "torccompat.h"
30 #include "torclocalcontext.h"
31 #include "torclanguage.h"
32 #include "torclogging.h"
33 #include "torcsetting.h"
34 #include "torcadminthread.h"
35 #include "torcnetwork.h"
36 #include "torchttprequest.h"
37 #include "torchtmlhandler.h"
38 #include "torchttphandler.h"
39 #include "torchttpservice.h"
40 #include "torchttpserver.h"
41 #include "torcbonjour.h"
42 #include "torcssdp.h"
43 #include "torcwebsockettoken.h"
44 #include "torchttpservernonce.h"
45 
46 QMap<QString,TorcHTTPHandler*> gHandlers;
47 QReadWriteLock* gHandlersLock = new QReadWriteLock(QReadWriteLock::Recursive);
49 
51  : port(0),
52  secure(false),
53  ipv4(true),
54  ipv6(true)
55 {
56 }
57 
59 {
60  return port == Other.port &&
61  secure == Other.secure &&
62  ipv4 == Other.ipv4 &&
63  ipv6 == Other.ipv6;
64 }
65 
67 {
68  bool changed = false;
69 
70  {
71  QWriteLocker locker(gHandlersLock);
72 
73  if (!Handler)
74  return;
75 
76  foreach (const QString &signature, Handler->Signature().split(','))
77  {
78  if (gHandlers.contains(signature))
79  {
80  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Handler '%1' for '%2' already registered - ignoring").arg(Handler->Name(), signature));
81  }
82  else if (!signature.isEmpty())
83  {
84  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Added handler '%1' for %2").arg(Handler->Name(), signature));
85  gHandlers.insert(signature, Handler);
86  changed = true;
87  }
88  }
89  }
90 
91  {
92  QMutexLocker locker(&gWebServerLock);
93  if (changed && gWebServer)
95  }
96 }
97 
99 {
100  bool changed = false;
101 
102  {
103  QWriteLocker locker(gHandlersLock);
104 
105  if (!Handler)
106  return;
107 
108  foreach (const QString &signature, Handler->Signature().split(','))
109  {
110  QMap<QString,TorcHTTPHandler*>::iterator it = gHandlers.find(signature);
111  if (it != gHandlers.end())
112  {
113  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Removing handler '%1'").arg(it.key()));
114  gHandlers.erase(it);
115  changed = true;
116  }
117  }
118  }
119 
120  {
121  QMutexLocker locker(&gWebServerLock);
122  if (changed && gWebServer)
123  emit gWebServer->HandlersChanged();
124  }
125 }
126 
127 void TorcHTTPServer::HandleRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request)
128 {
129  // this should already have been checked - but better safe than sorry
130  if (!Request.IsAuthorised())
131  return;
132 
133  {
134  QReadLocker locker(gHandlersLock);
135 
136  QString path = Request.GetPath();
137 
138  QMap<QString,TorcHTTPHandler*>::const_iterator it = gHandlers.constFind(path);
139  if (it != gHandlers.constEnd())
140  {
141  // direct path match
142  (*it)->ProcessHTTPRequest(PeerAddress, PeerPort, LocalAddress, LocalPort, Request);
143  }
144  else
145  {
146  // fully recursive handler
147  it = gHandlers.constBegin();
148  for ( ; it != gHandlers.constEnd(); ++it)
149  {
150  if ((*it)->GetRecursive() && path.startsWith(it.key()))
151  {
152  (*it)->ProcessHTTPRequest(PeerAddress, PeerPort, LocalAddress, LocalPort, Request);
153  break;
154  }
155  }
156  }
157  }
158 
159  // verify cross domain requests
161 }
162 
172 QVariantMap TorcHTTPServer::HandleRequest(const QString &Method, const QVariant &Parameters, QObject *Connection, bool Authenticated)
173 {
174  QReadLocker locker(gHandlersLock);
175 
176  QString path = QStringLiteral("/");
177  int index = Method.lastIndexOf(path);
178  if (index > -1)
179  path = Method.left(index + 1).trimmed();
180 
181  // ensure this is a services call
182  if (path.startsWith(gServicesDirectory))
183  {
184  // NB no recursive path handling here
185  QMap<QString,TorcHTTPHandler*>::const_iterator it = gHandlers.constFind(path);
186  if (it != gHandlers.constEnd())
187  return (*it)->ProcessRequest(Method, Parameters, Connection, Authenticated);
188  }
189 
190  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Method '%1' not found in services").arg(Method));
191 
192  QVariantMap result;
193  QVariantMap error;
194  error.insert(QStringLiteral("code"), -32601);
195  error.insert(QStringLiteral("message"), QStringLiteral("Method not found"));
196  result.insert(QStringLiteral("error"), error);
197  return result;
198 }
199 
201 {
202  QReadLocker locker(gHandlersLock);
203 
204  QVariantMap result;
205 
206  QMap<QString,TorcHTTPHandler*>::const_iterator it = gHandlers.constBegin();
207  for ( ; it != gHandlers.constEnd(); ++it)
208  {
209  TorcHTTPService *service = dynamic_cast<TorcHTTPService*>(it.value());
210  if (service)
211  {
212  QVariantMap map;
213  map.insert(QStringLiteral("path"), it.key());
214  map.insert(QStringLiteral("name"), service->GetUIName());
215  result.insert(it.value()->Name(), map);
216  }
217  }
218  return result;
219 }
220 
221 QVariantMap TorcHTTPServer::GetServiceDescription(const QString &Service)
222 {
223  QReadLocker locker(gHandlersLock);
224 
225  QMap<QString,TorcHTTPHandler*>::const_iterator it = gHandlers.constBegin();
226  for ( ; it != gHandlers.constEnd(); ++it)
227  {
228  if (it.value()->Name() == Service)
229  {
230  TorcHTTPService *service = dynamic_cast<TorcHTTPService*>(it.value());
231  if (service)
232  return service->GetServiceDetails();
233  }
234  }
235 
236  return QVariantMap();
237 }
238 
240 {
241  QMutexLocker locker(&gWebServerLock);
242  Status result = gWebServerStatus;
243  return result;
244 }
245 
247 {
248  return gPlatform;
249 }
250 
268 QMutex TorcHTTPServer::gWebServerLock(QMutex::Recursive);
269 QString TorcHTTPServer::gPlatform = QStringLiteral("");
270 QString TorcHTTPServer::gOriginWhitelist = QStringLiteral("");
271 QReadWriteLock TorcHTTPServer::gOriginWhitelistLock(QReadWriteLock::Recursive);
272 
274  : QObject(),
275  m_serverSettings(nullptr),
276  m_port(nullptr),
277  m_secure(nullptr),
278  m_upnp(nullptr),
279  m_upnpSearch(nullptr),
280  m_upnpAdvertise(nullptr),
281  m_bonjour(nullptr),
282  m_bonjourSearch(nullptr),
283  m_bonjourAdvert(nullptr),
284  m_ipv6(nullptr),
285  m_listener(nullptr),
286  m_user(),
287  m_defaultHandler(QStringLiteral(""), TORC_TORC), // default top level handler
288  m_servicesHandler(this), // services 'helper' service for '/services'
289  m_staticContent(), // static files - for /css /fonts /js /img etc
290  m_dynamicContent(), // dynamic files - for config files etc (typically served from ~/.torc/content)
291  m_upnpContent(), // upnp - device description
292  m_ssdpThread(nullptr),
293  m_bonjourBrowserReference(0),
294  m_httpBonjourReference(0),
295  m_torcBonjourReference(0),
296  m_webSocketPool()
297 {
298  // if app is running with root privilges (e.g. Raspberry Pi) then try and default to sensible port settings
299  // when first run. No point in trying 443 for secure sockets as SSL is not enabled by default (would require
300  // additional setup step).
301  m_serverSettings = new TorcSettingGroup(gLocalContext->GetRootSetting(), tr("Server"));
302  bool root = !geteuid();
303  m_port = new TorcSetting(m_serverSettings, TORC_PORT_SERVICE, tr("Port"), TorcSetting::Integer,
304  TorcSetting::Persistent | TorcSetting::Public, QVariant((int)(root ? 80 : 4840)));
305  m_port->SetRange(root ? 1 : 1024, 65535, 1);
306  m_port->SetActive(true);
307  connect(m_port, static_cast<void (TorcSetting::*)(int)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::PortChanged);
308  m_port->SetHelpText(tr("The port the server will listen on for incoming connections"));
309 
310  m_secure = new TorcSetting(m_serverSettings, TORC_SSL_SERVICE, tr("Secure sockets"), TorcSetting::Bool,
311  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)false));
312  m_secure->SetHelpText(tr("Use encrypted (SSL/TLS) connections to the server"));
313  m_secure->SetActive(true);
314  connect(m_secure, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::SecureChanged);
315 
316  m_upnp = new TorcSetting(m_serverSettings, QStringLiteral("ServerUPnP"), tr("UPnP"), TorcSetting::Bool,
317  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)true));
318  m_upnp->SetActive(true);
319  connect(m_upnp, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::UPnPChanged);
320 
321  m_upnpSearch = new TorcSetting(m_upnp, QStringLiteral("ServerUPnPSearch"), tr("UPnP Search"), TorcSetting::Bool,
322  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)true));
323  m_upnpSearch->SetHelpText(tr("Use UPnP to search for other devices"));
324  m_upnpSearch->SetActive(m_upnp->GetValue().toBool());
325  connect(m_upnpSearch, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::UPnPSearchChanged);
326  connect(m_upnp, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), m_upnpSearch, &TorcSetting::SetActive);
327 
328  m_upnpAdvertise = new TorcSetting(m_upnp, QStringLiteral("ServerUPnpAdvert"), tr("UPnP Advertisement"), TorcSetting::Bool,
329  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)true));
330  m_upnpAdvertise->SetHelpText(tr("Use UPnP to advertise this device"));
331  m_upnpAdvertise->SetActive(m_upnp->GetValue().toBool());
332  connect(m_upnpAdvertise, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::UPnPAdvertChanged);
333  connect(m_upnp, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), m_upnpAdvertise, &TorcSetting::SetActive);
334 
335  m_ipv6 = new TorcSetting(m_serverSettings, QStringLiteral("ServerIPv6"), tr("IPv6"), TorcSetting::Bool,
336  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)true));
337  m_ipv6->SetHelpText(tr("Enable IPv6"));
338  m_ipv6->SetActive(true);
339  connect(m_ipv6, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::IPv6Changed);
340 
341  m_bonjour = new TorcSetting(m_ipv6, QStringLiteral("ServerBonjour"), tr("Bonjour"), TorcSetting::Bool,
342  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)true));
343  m_bonjour->SetActive(m_ipv6->GetValue().toBool());
344  connect(m_bonjour, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::BonjourChanged);
345  connect(m_ipv6, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), m_bonjour, &TorcSetting::SetActive);
346 
347  m_bonjourSearch = new TorcSetting(m_bonjour, QStringLiteral("ServerBonjourSearch"), tr("Bonjour search"), TorcSetting::Bool,
348  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)true));
349  m_bonjourSearch->SetHelpText(tr("Use Bonjour to search for other devices"));
350  m_bonjourSearch->SetActiveThreshold(2);
351  m_bonjourSearch->SetActive(m_bonjour->GetValue().toBool());
352  m_bonjourSearch->SetActive(m_ipv6->GetValue().toBool());
353  connect(m_bonjourSearch, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::BonjourSearchChanged);
354  connect(m_bonjour, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), m_bonjourSearch, &TorcSetting::SetActive);
355  connect(m_ipv6, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), m_bonjourSearch, &TorcSetting::SetActive);
356 
357  m_bonjourAdvert = new TorcSetting(m_bonjour, QStringLiteral("ServerBonjourAdvert"), tr("Bonjour Advertisement"), TorcSetting::Bool,
358  TorcSetting::Persistent | TorcSetting::Public, QVariant((bool)true));
359  m_bonjourAdvert->SetHelpText(tr("Use Bonjour to advertise this device"));
360  m_bonjourAdvert->SetActiveThreshold(2);
361  m_bonjourAdvert->SetActive(m_bonjour->GetValue().toBool());
362  m_bonjourAdvert->SetActive(m_ipv6->GetValue().toBool());
363  connect(m_bonjourAdvert, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), this, &TorcHTTPServer::BonjourAdvertChanged);
364  connect(m_bonjour, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), m_bonjourAdvert, &TorcSetting::SetActive);
365  connect(m_ipv6, static_cast<void (TorcSetting::*)(bool)>(&TorcSetting::ValueChanged), m_bonjourAdvert, &TorcSetting::SetActive);
366 
367  // initialise external status
368  {
369  QMutexLocker locker(&gWebServerLock);
370  gWebServerStatus.secure = m_secure->GetValue().toBool();
371  gWebServerStatus.port = m_port->GetValue().toInt();
372  gWebServerStatus.ipv6 = m_ipv6->GetValue().toBool();
373  }
374 
375  // initialise platform name
376  static bool initialised = false;
377  if (!initialised)
378  {
379  initialised = true;
380  gPlatform = QStringLiteral("%1, Version: %2 ").arg(TORC_TORC, QStringLiteral(GIT_VERSION));
381 #ifdef Q_OS_WIN
382  gPlatform += QStringLiteral("(Windows %1.%2)").arg(LOBYTE(LOWORD(GetVersion()))).arg(HIBYTE(LOWORD(GetVersion())));
383 #else
384 #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
385  gPlatform += QStringLiteral("(%1 %2)").arg(QSysInfo::kernelType(), QSysInfo::kernelVersion());
386 #else
387  gPlatform += QStringLiteral("(OldTorc OldVersion)");
388 #endif
389 #endif
390  }
391 
392  // listen for host name updates
393  gLocalContext->AddObserver(this);
394 
395  // and start
396  Open();
397 }
398 
400 {
401  disconnect();
402 
404 
405  Close();
406 
407  if (m_bonjourAdvert)
408  {
409  m_bonjourAdvert->Remove();
410  m_bonjourAdvert->DownRef();
411  m_bonjourAdvert = nullptr;
412  }
413 
414  if (m_bonjourSearch)
415  {
416  m_bonjourSearch->Remove();
417  m_bonjourSearch->DownRef();
418  m_bonjourSearch = nullptr;
419  }
420 
421  if (m_bonjour)
422  {
423  m_bonjour->Remove();
424  m_bonjour->DownRef();
425  m_bonjour = nullptr;
426  }
427 
428  if (m_ipv6)
429  {
430  m_ipv6->Remove();
431  m_ipv6->DownRef();
432  m_ipv6 = nullptr;
433  }
434 
435  if (m_upnpAdvertise)
436  {
437  m_upnpAdvertise->Remove();
438  m_upnpAdvertise->DownRef();
439  m_upnpAdvertise = nullptr;
440  }
441 
442  if (m_upnpSearch)
443  {
444  m_upnpSearch->Remove();
445  m_upnpSearch->DownRef();
446  m_upnpSearch = nullptr;
447  }
448 
449  if (m_upnp)
450  {
451  m_upnp->Remove();
452  m_upnp->DownRef();
453  m_upnp = nullptr;
454  }
455 
456  if (m_port)
457  {
458  m_port->Remove();
459  m_port->DownRef();
460  m_port = nullptr;
461  }
462 
463  if (m_secure)
464  {
465  m_secure->Remove();
466  m_secure->DownRef();
467  m_secure = nullptr;
468  }
469 
470  if (m_serverSettings)
471  {
472  m_serverSettings->Remove();
473  m_serverSettings->DownRef();
474  m_serverSettings = nullptr;
475  }
476 
478 }
479 
481 {
482  QMutexLocker locker(&gWebServerLock);
483  if (gWebServer)
484  return gWebServer->TakeSocketPriv(Socket);
485  return nullptr;
486 }
487 
495 void TorcHTTPServer::Authorise(const QString &Host, TorcHTTPRequest &Request, bool ForceCheck)
496 {
497  // N.B. the order of the following checks is critical. Always check the Authorization header
498  // first and accesstokens before PreAuthorisations as PreAuthorisations are not checked again.
499 
500  // explicit authorization header
501  // N.B. This will also authorise websocket clients who send authorisation headers with
502  // the upgrade request i.e. there is no need to use the accesstoken route IF the correct headers
503  // are sent. Use GetWebSocketToken, which requires authorisation, to force
504  // clients to authenticate (i.e. browsers).
506  if (Request.IsAuthorised() == HTTPAuthorised)
507  return;
508 
509  if (Request.IsAuthorised() == HTTPAuthorisedStale)
510  {
511  AddAuthenticationHeader(Request);
512  return;
513  }
514 
515  // authentication token supplied in the url (WebSocket)
516  // do this before 'standard' authentication to ensure token is checked and expired
517  // NB ensure method is empty to stop attempts to access other resources - although
518  // upgrade requests are handled before anything else, so the method should be ignored
519  if (Request.Queries().contains(QStringLiteral("accesstoken")) && Request.GetMethod().isEmpty())
520  {
521  QString token = Request.Queries().value(QStringLiteral("accesstoken"));
522  if (token == TorcWebSocketToken::GetWebSocketToken(Host, token))
523  {
524  Request.Authorise(HTTPAuthorised);
525  return;
526  }
527  }
528 
529  // We allow unauthenticated access to anything that doesn't alter state unless auth is specifically
530  // requested in the service (see TorcHTTPServices::GetWebSocketToken).
531  // N.B. If a rogue peer posts to a 'setter' type function using a GET type request, the request
532  // will fail during processing - so don't validate the method here as it may lead to false positives.
533  // ForceCheck is used to process any authentication headers for WebSocket upgrade requests.
534  HTTPRequestType type = Request.GetHTTPRequestType();
535  bool authenticate = type == HTTPPost || type == HTTPPut || type == HTTPDelete || type == HTTPUnknownType;
536 
537  if (!authenticate && !ForceCheck)
538  {
539  Request.Authorise(HTTPPreAuthorised);
540  return;
541  }
542 
543  AddAuthenticationHeader(Request);
544 }
545 
547 {
549  Request.SetStatus(HTTP_Unauthorized);
550 
551  // HTTP 1.1 should support Digest
552  if (true /*Request.GetHTTPProtocol() > HTTPOneDotZero*/)
553  {
555  }
556  else // otherwise fallback to Basic
557  {
558  Request.SetResponseHeader(QStringLiteral("WWW-Authenticate"), QStringLiteral("Basic realm=\"%1\"").arg(TORC_REALM));
559  }
560 }
561 
570 {
571  gOriginWhitelistLock.lockForRead();
572  bool origin = gOriginWhitelist.contains(Request.Headers().value(QStringLiteral("Origin")), Qt::CaseInsensitive);
573  gOriginWhitelistLock.unlock();
574 
575  if (Request.Headers().contains(QStringLiteral("Origin")) && (origin || Request.GetAllowCORS()))
576  {
577  Request.SetResponseHeader(QStringLiteral("Access-Control-Allow-Origin"), Request.Headers().value(QStringLiteral("Origin")));
578  if (Request.Headers().contains(QStringLiteral("Access-Control-Allow-Credentials")))
579  Request.SetResponseHeader(QStringLiteral("Access-Control-Allow-Credentials"), QStringLiteral("true"));
580  if (Request.Headers().contains(QStringLiteral("Access-Control-Request-Headers")))
581  Request.SetResponseHeader(QStringLiteral("Access-Control-Request-Headers"), QStringLiteral("Origin, X-Requested-With, Content-Type, Accept, Range"));
582  if (Request.Headers().contains(QStringLiteral("Access-Control-Request-Method")))
583  Request.SetResponseHeader(QStringLiteral("Access-Control-Request-Method"), TorcHTTPRequest::AllowedToString(HTTPGet | HTTPOptions | HTTPHead));
584  Request.SetResponseHeader(QStringLiteral("Access-Control-Max-Age"), QString::number(86400));
585  }
586 }
587 
589 {
590  if (!Request.Headers().contains(QStringLiteral("Authorization")))
591  return;
592 
593  QString header = Request.Headers().value(QStringLiteral("Authorization"));
594 
595  // most clients will support Digest Access Authentication, so try that first
596  if (header.startsWith(QStringLiteral("Digest"), Qt::CaseInsensitive))
597  {
599  }
600  /* Don't use Basic. It is utterly insecure, everyone supports Digest and we store the username/password as a Digest compatible hash.
601  else if (header.startsWith("Basic", Qt::CaseInsensitive))
602  {
603  // only accept Basic authentication if using < HTTP 1.1
604  if (Request.GetHTTPProtocol() > HTTPOneDotZero)
605  {
606  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Disallowing basic authentication for HTTP 1.1 client"));
607  }
608  else
609  {
610  // remove leading 'Basic' and split off token
611  QString authentication = header.mid(5).trimmed();
612  QStringList userinfo = QStringLiteral(QByteArray::fromBase64(authentication.toUtf8())).split(':');
613  if ((userinfo.size() == 2) && (userinfo[0] == username) && (userinfo[1] == password))
614  Request.Authorise(HTTPAuthorised);
615  }
616  }
617  */
618 }
619 
624 void TorcHTTPServer::UpdateOriginWhitelist(TorcHTTPServer::Status Status)
625 {
626  QWriteLocker locker(&gOriginWhitelistLock);
627 
628  QString protocol = Status.secure ? QStringLiteral("https://") : QStringLiteral("http://");
629 
630  // localhost first
631  gOriginWhitelist = QStringLiteral("%1localhost:%2 ").arg(protocol).arg(Status.port);
632 
633  // all known raw IP addresses
634  QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
635  for (int i = 0; i < addresses.size(); ++i)
636  gOriginWhitelist += QStringLiteral("%1%2 ").arg(protocol, TorcNetwork::IPAddressToLiteral(addresses[i], Status.port, false));
637 
638  // and any known host names
639  QStringList hosts = TorcNetwork::GetHostNames();
640  foreach (const QString &host, hosts)
641  {
642  QString newhost = QStringLiteral("%1%2:%3 ").arg(protocol, host).arg(Status.port);
643  if (!host.isEmpty() && !gOriginWhitelist.contains(newhost))
644  gOriginWhitelist += newhost;
645  }
646 
647  LOG(VB_NETWORK, LOG_INFO, QStringLiteral("Origin whitelist: %1").arg(gOriginWhitelist));
648 }
649 
650 void TorcHTTPServer::StartBonjour(void)
651 {
652  if (!m_bonjour->GetValue().toBool())
653  return;
654 
655  if (!m_ipv6->GetValue().toBool())
656  {
657  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Not using Bonjour while IPv6 is disabled"));
658  StopBonjour();
659  return;
660  }
661 
662  if (!m_bonjourBrowserReference && m_bonjourSearch->GetValue().toBool())
663  m_bonjourBrowserReference = TorcBonjour::Instance()->Browse("_torc._tcp.");
664 
665  if ((!m_httpBonjourReference || !m_torcBonjourReference) && m_bonjourAdvert->GetValue().toBool())
666  {
667  int port = m_port->GetValue().toInt();
668  QMap<QByteArray,QByteArray> map;
669  map.insert(TORC_UUID_B, gLocalContext->GetUuid().toLatin1());
670  map.insert(TORC_APIVERSION_B, TorcHTTPServices::GetVersion().toLocal8Bit().constData());
671  map.insert(TORC_PRIORITY_B, QByteArray::number(gLocalContext->GetPriority()));
672  map.insert(TORC_STARTTIME_B, QByteArray::number(gLocalContext->GetStartTime()));
673  if (m_secure->GetValue().toBool())
674  map.insert(TORC_SECURE_B, QByteArrayLiteral("yes"));
675 
676  QString name = ServerDescription();
677 
678  if (!m_httpBonjourReference)
679  m_httpBonjourReference = TorcBonjour::Instance()->Register(port, m_secure->GetValue().toBool() ? QByteArrayLiteral("_https._tcp") : QByteArrayLiteral("_http._tcp"), name.toLocal8Bit().constData(), map);
680 
681  if (!m_torcBonjourReference)
682  m_torcBonjourReference = TorcBonjour::Instance()->Register(port, QByteArrayLiteral("_torc._tcp"), name.toLocal8Bit().constData(), map);
683  }
684 }
685 
686 void TorcHTTPServer::StopBonjour(void)
687 {
688  StopBonjourAdvert();
689  StopBonjourBrowse();
690 }
691 
692 void TorcHTTPServer::StopBonjourBrowse(void)
693 {
694  if (m_bonjourBrowserReference)
695  {
696  TorcBonjour::Instance()->Deregister(m_bonjourBrowserReference);
697  m_bonjourBrowserReference = 0;
698  }
699 }
700 
701 void TorcHTTPServer::StopBonjourAdvert(void)
702 {
703  if (m_httpBonjourReference)
704  {
705  TorcBonjour::Instance()->Deregister(m_httpBonjourReference);
706  m_httpBonjourReference = 0;
707  }
708 
709  if (m_torcBonjourReference)
710  {
711  TorcBonjour::Instance()->Deregister(m_torcBonjourReference);
712  m_torcBonjourReference = 0;
713  }
714 }
715 
716 #define en QStringLiteral("en")
717 #define dis QStringLiteral("dis")
718 
720 {
721  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Bonjour %1abled").arg(Bonjour ? en : dis));
722 
723  if (Bonjour)
724  StartBonjour();
725  else
726  StopBonjour();
727 }
728 
730 {
731  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Bonjour advertisement %1abled").arg(Advert ? en : dis));
732  if (Advert)
733  StartBonjour();
734  else
735  StopBonjourAdvert();
736 }
737 
739 {
740  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Bonjour search %1abled").arg(Search ? en : dis));
741  if (Search)
742  StartBonjour();
743  else
744  StopBonjourBrowse();
745 }
746 
748 {
749  {
750  QMutexLocker lock(&gWebServerLock);
752  }
753 
754  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("IPv6 %1abled - restarting").arg(IPv6 ? en : dis));
755  QTimer::singleShot(10, this, &TorcHTTPServer::Restart);
756 }
757 
759 {
760  if (m_listener)
761  {
762  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("HTTP server alreay listening - closing"));
763  Close();
764  }
765 
766  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSL is %1abled").arg(m_secure->GetValue().toBool() ? en : dis));
767  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("IPv6 is %1abled").arg(m_ipv6->GetValue().toBool() ? en : dis));
768  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Bonjour is %1abled").arg(m_bonjour->GetValue().toBool() ? en : dis));
769  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Bonjour search is %1abled").arg(m_bonjourSearch->GetValue().toBool() ? en : dis));
770  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Bonjour advertisement is %1abled").arg(m_bonjourAdvert->GetValue().toBool() ? en : dis));
771  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSDP is %1abled").arg(m_upnp->GetValue().toBool() ? en : dis));
772  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSDP search is %1abled").arg(m_upnpSearch->GetValue().toBool() ? en : dis));
773  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSDP advertisement is %1abled").arg(m_upnpAdvertise->GetValue().toBool() ? en : dis));
774  int port = m_port->GetValue().toInt();
775  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Attempting to listen on port %1").arg(port));
776 
777  m_listener = new TorcHTTPServerListener(this, m_ipv6->GetValue().toBool() ? QHostAddress::Any : QHostAddress::AnyIPv4, port);
778  if (!m_listener->isListening() && !m_listener->Listen(m_ipv6->GetValue().toBool() ? QHostAddress::Any : QHostAddress::AnyIPv4))
779  {
780  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to open web server port"));
781  Close();
782  return false;
783  }
784 
785  // try to use the same port
786  if (port != m_listener->serverPort())
787  {
788  QMutexLocker locker(&gWebServerLock);
789  port = m_listener->serverPort();
790  m_port->SetValue(QVariant((int)port));
791  gWebServerStatus.port = port;
792  }
793 
794  // advertise and search
795  StartBonjour();
796  StartUPnP();
797 
798  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Web server listening for %1secure connections on port %2").arg(m_secure->GetValue().toBool() ? QStringLiteral("") : QStringLiteral("in")).arg(port));
799  UpdateOriginWhitelist(TorcHTTPServer::GetStatus());
800  return true;
801 }
802 
804 {
805  QString host = QHostInfo::localHostName();
806  if (host.isEmpty())
807  host = tr("Unknown");
808  return QStringLiteral("%1@%2").arg(QCoreApplication::applicationName(), host);
809 }
810 
812 {
813  // stop advertising and searching
814  StopBonjour();
815  StopUPnP();
816 
817  // close connections
818  m_webSocketPool.CloseSockets();
819 
820  // actually close
821  if (m_listener)
822  delete m_listener;
823  m_listener = nullptr;
824 
825  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Webserver closed"));
826 }
827 
829 {
830  {
831  QMutexLocker lock(&gWebServerLock);
832  gWebServerStatus.port = Port;
833  }
834 
835  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Port changed to %1 - restarting").arg(Port));
836  QTimer::singleShot(10, this, &TorcHTTPServer::Restart);
837 }
838 
840 {
841  {
842  QMutexLocker lock(&gWebServerLock);
843  gWebServerStatus.secure = m_secure->GetValue().toBool();
844  }
845 
846  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Secure %1abled - restarting").arg(Secure ? en : dis));
847  QTimer::singleShot(10, this, &TorcHTTPServer::Restart);
848 }
849 
850 void TorcHTTPServer::StartUPnP(void)
851 {
852  if (m_upnp->GetValue().toBool())
853  {
854  bool search = m_upnpSearch->GetValue().toBool();
855  bool advert = m_upnpAdvertise->GetValue().toBool();
856 
857  if (!m_ssdpThread && (search || advert))
858  {
859  m_ssdpThread = new TorcSSDPThread();
860  m_ssdpThread->start();
861  }
862 
863  if (search)
865  if (advert)
867  }
868 }
869 
870 void TorcHTTPServer::StopUPnP(void)
871 {
874 
875  if (m_ssdpThread)
876  {
877  m_ssdpThread->quit();
878  m_ssdpThread->wait();
879  delete m_ssdpThread;
880  m_ssdpThread = nullptr;
881  }
882 }
883 
885 {
886  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSDP %1abled").arg(UPnP ? en : dis));
887 
888  if (UPnP)
889  StartUPnP();
890  else
891  StopUPnP();
892 }
893 
895 {
896  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSDP advertisement %1abled").arg(Advert ? en : dis));
897  if (Advert)
898  {
899  StartUPnP();
900  }
901  else
902  {
903  if (m_upnpSearch->GetValue().toBool())
905  else
906  StopUPnP();
907  }
908 }
909 
911 {
912  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSDP search %1abled").arg(Search ? en : dis));
913  if (Search)
914  {
915  StartUPnP();
916  }
917  else
918  {
919  if (m_upnpAdvertise->GetValue().toBool())
921  else
922  StopUPnP();
923  }
924 }
925 
927 {
928  Close();
929  Open();
930 }
931 
932 bool TorcHTTPServer::event(QEvent *Event)
933 {
934  if (Event->type() == TorcEvent::TorcEventType)
935  {
936  TorcEvent* torcevent = dynamic_cast<TorcEvent*>(Event);
937  if (torcevent)
938  {
939  switch (torcevent->GetEvent())
940  {
941  // these events all notify that the list of interfaces and addresses the server is
942  // listening on will have been updated
947  UpdateOriginWhitelist(TorcHTTPServer::GetStatus());
948  return true;
949  break;
950  case Torc::UserChanged:
951  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("User name/credentials changed - restarting webserver"));
952  Restart();
953  return true;
954  break;
955  default:
956  break;
957  }
958  }
959  }
960 
961  return QObject::event(Event);
962 }
963 
965 {
966  return m_webSocketPool.TakeSocket(Socket);
967 }
968 
969 void TorcHTTPServer::NewConnection(qintptr SocketDescriptor)
970 {
971  m_webSocketPool.IncomingConnection(SocketDescriptor, m_secure->GetValue().toBool());
972 }
973 
975 {
976  public:
979  {
980  qRegisterMetaType<TorcHTTPRequest*>();
981  qRegisterMetaType<TorcHTTPService*>();
982  qRegisterMetaType<QTcpSocket*>();
983  qRegisterMetaType<QHostAddress>();
984  }
985 
986  void GetStrings(QVariantMap &Strings)
987  {
988  Strings.insert(QStringLiteral("ServerApplication"), QCoreApplication::applicationName());
989  Strings.insert(QStringLiteral("SocketNotConnected"), QCoreApplication::translate("TorcHTTPServer", "Not connected"));
990  Strings.insert(QStringLiteral("SocketConnecting"), QCoreApplication::translate("TorcHTTPServer", "Connecting"));
991  Strings.insert(QStringLiteral("SocketConnected"), QCoreApplication::translate("TorcHTTPServer", "Connected"));
992  Strings.insert(QStringLiteral("SocketReady"), QCoreApplication::translate("TorcHTTPServer", "Ready"));
993  Strings.insert(QStringLiteral("SocketDisconnecting"), QCoreApplication::translate("TorcHTTPServer", "Disconnecting"));
994  Strings.insert(QStringLiteral("ConnectedTo"), QCoreApplication::translate("TorcHTTPServer", "Connected to"));
995  Strings.insert(QStringLiteral("ConnectedSecureTo"), QCoreApplication::translate("TorcHTTPServer", "Connected securely to"));
996  Strings.insert(QStringLiteral("ConnectTo"), QCoreApplication::translate("TorcHTTPServer", "Connect to"));
997 
998  Strings.insert(QStringLiteral("SocketReconnectAfterMs"), 10000); // try and reconnect every 10 seconds
999  }
1000 
1001  void Create(void)
1002  {
1003  Destroy();
1004 
1005  QMutexLocker locker(&TorcHTTPServer::gWebServerLock);
1006  TorcHTTPServer::gWebServer = new TorcHTTPServer();
1007  }
1008 
1009  void Destroy(void)
1010  {
1011  QMutexLocker locker(&TorcHTTPServer::gWebServerLock);
1012 
1013  // this may need to use deleteLater but this causes issues with setting deletion
1014  // as the object is actually destroyed after the parent (network) setting
1015  if (TorcHTTPServer::gWebServer)
1017 
1018  TorcHTTPServer::gWebServer = nullptr;
1019  }
1021 
1022 
bool Listen(const QHostAddress &Address, int Port=0)
bool operator==(Status Other) const
void Restart(void)
bool GetAllowCORS(void) const
A class to encapsulate an incoming HTTP request.
void IncomingConnection(qintptr SocketDescriptor, bool Secure)
HTTPRequestType GetHTTPRequestType(void) const
void SetHelpText(const QString &HelpText)
QReadWriteLock * gHandlersLock
void UPnPAdvertChanged(bool Advert)
TorcLocalContext * gLocalContext
QVariant GetValue(void)
High level group of related settings.
Definition: torcsetting.h:123
Base HTTP response handler class.
static void Authorise(const QString &Host, TorcHTTPRequest &Request, bool ForceCheck)
Ensures remote user is authorised to access this request.
#define TORC_PRIORITY_B
Definition: torcupnp.h:28
void Deregister(quint32 Reference)
Cancel a Bonjour service registration or browse request.
QString Signature(void) const
#define TORC_PORT_SERVICE
Definition: torclocaldefs.h:14
A wrapper around a database setting.
Definition: torcsetting.h:15
#define TORC_APIVERSION_B
Definition: torcupnp.h:27
static QMutex gWebServerLock
A factory class for automatically running objects outside of the main loop.
int GetEvent(void)
Return the Torc action associated with this event.
Definition: torcevent.cpp:65
static void ProcessDigestAuth(TorcHTTPRequest &Request, bool Check=false)
A server nonce for Digest Access Authentication.
static void AddAuthenticationHeader(TorcHTTPRequest &Request)
#define TORC_SERVICES_DIR
Definition: torclocaldefs.h:12
TorcWebSocketThread * TakeSocketPriv(TorcWebSocketThread *Socket)
bool event(QEvent *Event) overridefinal
QMap< QString, TorcHTTPHandler * > gHandlers
#define en
static QStringList GetHostNames(void)
Retrieve the list of currently identified host names.
static void CancelAnnounce(void)
Definition: torcssdp.cpp:305
static void HandleRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request)
QString GetUuid(void) const
quint32 Browse(const QByteArray &Type, quint32 Reference=0)
Search for a service advertised via Bonjour.
static QString IPAddressToLiteral(const QHostAddress &Address, int Port, bool UseLocalhost=true)
Convert an IP address to a string literal.
void BonjourSearchChanged(bool Search)
void GetStrings(QVariantMap &Strings)
static void ValidateOrigin(TorcHTTPRequest &Request)
Check the Origin header for validity and respond appropriately.
void BonjourAdvertChanged(bool Advert)
virtual bool DownRef(void)
void HandlersChanged(void)
HTTPAuthorisation IsAuthorised(void) const
static QString gPlatform
virtual QString GetUIName(void)
#define TORC_ADMIN_HIGH_PRIORITY
void BonjourChanged(bool Bonjour)
void UPnPSearchChanged(bool Search)
static QVariantMap GetServiceHandlers(void)
void Remove(void)
static void RegisterHandler(TorcHTTPHandler *Handler)
void PortChanged(int Port)
QString GetPath(void) const
const QMap< QString, QString > & Headers(void) const
static void CancelSearch(void)
Stop searching for a UPnP device type.
Definition: torcssdp.cpp:283
static void Announce(TorcHTTPServer::Status Options)
Publish the device.
Definition: torcssdp.cpp:294
VERBOSE_PREAMBLE true
QString GetMethod(void) const
QString Name(void) const
static Type TorcEventType
Register TorcEventType with QEvent.
Definition: torcevent.h:19
#define TORC_UUID_B
Definition: torcupnp.h:26
static Status gWebServerStatus
static void Search(TorcHTTPServer::Status Options)
Search for Torc UPnP device type.
Definition: torcssdp.cpp:269
static Status GetStatus(void)
static void AuthenticateUser(TorcHTTPRequest &Request)
static QString gOriginWhitelist
static void TearDown(void)
Destroys the global TorcBonjour singleton.
quint32 Register(quint16 Port, const QByteArray &Type, const QByteArray &Name, const QMap< QByteArray, QByteArray > &TxtRecords, quint32 Reference=0)
void SetResponseHeader(const QString &Header, const QString &Value)
#define TORC_TORC
Definition: torclocaldefs.h:8
static void DeregisterHandler(TorcHTTPHandler *Handler)
static TorcBonjour * Instance(void)
Returns the global TorcBonjour singleton.
A general purpose event object.
Definition: torcevent.h:9
QString gServicesDirectory(TORC_SERVICES_DIR)
bool SetValue(const QVariant &Value)
static QString GetVersion(void)
void SetStatus(HTTPStatus Status)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
static QString PlatformName(void)
void AddObserver(QObject *Observer)
brief Register the given object to receive events.
#define IPv6
Definition: torcssdp.cpp:331
#define TORC_REALM
Definition: torclocaldefs.h:9
A factory class to register translatable strings for use with external interfaces/applications.
Definition: torclanguage.h:66
void SetActiveThreshold(int Threshold)
HTTPRequestType
void SetRange(int Begin, int End, int Step)
void SetResponseType(HTTPResponseType Type)
static TorcHTTPServer * gWebServer
TorcSetting * GetRootSetting(void)
void IPv6Changed(bool IPv6)
void ValueChanged(int Value)
Wraps a TorcQThread around a TorcWebsocket.
QVariantMap GetServiceDetails(void)
Return a QVariantMap describing the services methods and properties.
qint64 GetStartTime(void)
void RemoveObserver(QObject *Observer)
brief Deregister the given object.
friend class TorcHTTPServerObject
void NewConnection(qintptr SocketDescriptor)
static QString ServerDescription(void)
TorcWebSocketThread * TakeSocket(TorcWebSocketThread *Socket)
#define dis
An HTTP server.
void UPnPChanged(bool UPnP)
void SecureChanged(bool Secure)
#define TORC_SECURE_B
Definition: torcupnp.h:30
static TorcWebSocketThread * TakeSocket(TorcWebSocketThread *Socket)
const QMap< QString, QString > & Queries(void) const
void Authorise(HTTPAuthorisation Authorisation)
static QVariantMap GetServiceDescription(const QString &Service)
#define TORC_SSL_SERVICE
Definition: torclocaldefs.h:15
static QReadWriteLock gOriginWhitelistLock
static QString AllowedToString(int Allowed)
virtual ~TorcHTTPServer()
#define TORC_STARTTIME_B
Definition: torcupnp.h:29
static QString GetWebSocketToken(const QString &Host, const QString &Current=QString())
Retrieve an authentication token for the given request or validate a current token.
void SetActive(bool Value)