MythTV master
mythhttpserver.cpp
Go to the documentation of this file.
1// Qt
2#include <QtGlobal>
3#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
4#include <QtSystemDetection>
5#endif
6#include <QDirIterator>
7#include <QNetworkInterface>
8#include <QCoreApplication>
9#include <QSslKey>
10#include <QSslCipher>
11#include <QSslCertificate>
12
13// MythTV
14#include "mythconfig.h"
15#include "mythversion.h"
16#include "mythdirs.h"
17#include "mythcorecontext.h"
18#include "mythlogging.h"
20#if CONFIG_LIBDNS_SD
21#include "bonjourregister.h"
22#endif
23#include "http/mythhttpsocket.h"
25#include "http/mythhttpthread.h"
26#include "http/mythhttps.h"
27#include "http/mythhttpserver.h"
28
29// Std
30#ifndef Q_OS_WINDOWS
31#include <sys/utsname.h>
32#endif
33
34#define LOC QString("HTTPServer: ")
35
37{
38 // Join the dots
50
51 // Find our static content
53 while (m_config.m_rootDir.endsWith("/"))
54 m_config.m_rootDir.chop(1);
55 m_config.m_rootDir.append(QStringLiteral("/html"));
56
57 // Add our default paths (mostly static js, css, images etc).
58 // We need to pass individual directories to the threads, so inspect the
59 // the paths we want for sub-directories
60 static const QStringList s_dirs = { "/assets/", "/3rdParty/", "/css/", "/images/", "/apps/", "/xslt/" };
61 m_config.m_filePaths.clear();
62 QStringList dirs;
63 for (const auto & dir : s_dirs)
64 {
65 dirs.append(dir);
66 QDirIterator it(m_config.m_rootDir + dir, QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
67 while (it.hasNext())
68 dirs.append(it.next().remove(m_config.m_rootDir) + "/");
69 }
70
71 // And finally the root handler
72 dirs.append("/");
73 NewPaths(dirs);
74}
75
77{
78 Stopped();
79}
80
82{
83 if (Enable && !isListening())
84 {
85 Init();
86 bool tcp = m_config.m_port != 0;
87 bool ssl = m_config.m_sslPort != 0;
88
89 if (tcp)
90 {
91 tcp = listen(m_config.m_port);
92 // ServerPool as written will overwrite the port setting if we listen
93 // on an additional port (i.e. SSL). So check which port is in use before
94 // continuing
95 if (m_config.m_port != serverPort())
96 {
97 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Server is using port %1 - expected %2")
98 .arg(serverPort()).arg(m_config.m_port));
100 }
101 if (m_config.m_port_2)
103 }
104
105 if (ssl)
106 {
107 ssl = listen(m_config.m_sslPort, true, kSSLServer);
109 {
110 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Server is using port %1 - expected %2")
111 .arg(serverPort()).arg(m_config.m_sslPort));
113 }
114 }
115
116 Started(tcp, ssl);
117 }
118 else if (!Enable && isListening())
119 {
120 close();
121 Stopped();
122 }
123}
124
131{
132 // Just in case we get in a mess
133 Stopped();
134
135 // Decide on the ports to use
137 {
138 m_config.m_port = XmlConfiguration().GetValue("UPnP/MythFrontend/ServicePort", 6547);
139 // I don't think there is an existing setting for this
140 m_config.m_sslPort = static_cast<uint16_t>(gCoreContext->GetNumSetting("FrontendSSLPort", m_config.m_port + 10));
141
142 }
143 else if (gCoreContext->IsBackend())
144 {
146 // Additional port, may be removed later
148 m_config.m_sslPort = static_cast<uint16_t>(gCoreContext->GetNumSetting("BackendSSLPort", m_config.m_port + 10));
149 }
150 else
151 {
152 // N.B. This assumes only the frontend and backend use HTTP...
153 m_config.m_port = 0;
155 }
156
157 // If this fails, unset the SSL port
158#ifndef QT_NO_OPENSSL
160#endif
161 {
163 }
164
165 if (m_config.m_sslPort == 0)
166 LOG(VB_HTTP, LOG_INFO, LOC + "SSL is disabled");
167
168 // Set the server ident
169 QString version = GetMythSourceVersion();
170 if (version.startsWith("v"))
171 version = version.right(version.length() - 1);
172
173#ifdef Q_OS_WINDOWS
174 QString server = QStringLiteral("Windows");
175#else
176 struct utsname uname_info {};
177 uname(&uname_info);
178 QString server = QStringLiteral("%1/%2").arg(uname_info.sysname, uname_info.release);
179#endif
180 m_config.m_serverName = QString("MythTV/%1 %2 UPnP/1.0").arg(version, server);
181
182 // Retrieve language
184
185 // Get master backend details for Origin checks
189
190 // Get keep alive timeout
191 auto timeout = gCoreContext->GetNumSetting("HTTP/KeepAliveTimeoutSecs", HTTP_SOCKET_TIMEOUT_MS / 1000);
192 m_config.m_timeout = static_cast<std::chrono::milliseconds>(timeout * 1000);
193}
194
195void MythHTTPServer::Started([[maybe_unused]] bool Tcp,
196 [[maybe_unused]] bool Ssl)
197{
198#if CONFIG_LIBDNS_SD
199 // Advertise our webserver
200 delete m_bonjour;
201 delete m_bonjourSSL;
202 m_bonjour = nullptr;
203 m_bonjourSSL = nullptr;
204 if (!(Tcp || Ssl))
205 return;
206
207 auto host = QHostInfo::localHostName();
208 if (host.isEmpty())
209 host = tr("Unknown");
210
211 if (Tcp)
212 {
213 m_bonjour = new BonjourRegister();
214 m_bonjour->Register(m_config.m_port, QByteArrayLiteral("_http._tcp"),
215 QStringLiteral("%1 on %2").arg(QCoreApplication::applicationName(), host).toLatin1().constData(), {});
216 }
217
218 if (Ssl)
219 {
220 m_bonjourSSL = new BonjourRegister();
221 m_bonjourSSL->Register(m_config.m_sslPort, QByteArrayLiteral("_https._tcp"),
222 QStringLiteral("%1 on %2").arg(QCoreApplication::applicationName(), host).toLatin1().constData(), {});
223 }
224#endif
225
226 // Build our list of hosts and allowed origins.
227 BuildHosts();
228 BuildOrigins();
229}
230
232{
233 // Clear allowed origins
234 m_config.m_hosts.clear();
236
237#if CONFIG_LIBDNS_SD
238 // Stop advertising
239 delete m_bonjour;
240 delete m_bonjourSSL;
241 m_bonjour = nullptr;
242 m_bonjourSSL = nullptr;
243#endif
244}
245
247{
248 if (!m_connectionQueue.empty())
249 {
250 emit ProcessTCPQueue();
251 }
252}
253
255{
256 if (AvailableThreads() > 0)
257 {
258 auto entry = m_connectionQueue.dequeue();
260 auto name = QString("HTTP%1%2").arg(entry.m_ssl ? "S" : "").arg(m_threadNum++);
261 auto * newthread = new MythHTTPThread(this, m_config, name, entry.m_socketFD, entry.m_ssl);
262 AddThread(newthread);
263 connect(newthread->qthread(), &QThread::finished, this, &MythHTTPThreadPool::ThreadFinished);
264 connect(newthread->qthread(), &QThread::finished, this, &MythHTTPServer::ThreadFinished);
265 newthread->start();
266 return;
267 }
268}
269
271{
272 if (!Socket)
273 return;
274 auto * server = qobject_cast<PrivTcpServer*>(QObject::sender());
275 MythTcpQueueEntry entry;
276 entry.m_socketFD = Socket;
277 entry.m_ssl = (server->GetServerType() == kSSLServer);
278 m_connectionQueue.enqueue(entry);
279 emit ProcessTCPQueue();
280}
281
282bool MythHTTPServer::ReservedPath(const QString& Path)
283{
284 static const QStringList s_reservedPaths { HTTP_SERVICES_DIR };
285 if (s_reservedPaths.contains(Path, Qt::CaseInsensitive))
286 {
287 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Server path '%1' is reserved - ignoring").arg(Path));
288 return true;
289 }
290 return false;
291}
292
314void MythHTTPServer::NewPaths(const QStringList &Paths)
315{
316 if (Paths.isEmpty())
317 return;
318 for (const auto & path : std::as_const(Paths))
319 {
320 if (ReservedPath(path))
321 continue;
322 if (m_config.m_filePaths.contains(path))
323 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("'%1' is already registered").arg(path));
324 else
325 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding path: '%1'").arg(path));
326 m_config.m_filePaths.append(path);
327 }
329}
330
331void MythHTTPServer::StalePaths(const QStringList& Paths)
332{
333 if (Paths.isEmpty())
334 return;
335 for (const auto & path : std::as_const(Paths))
336 {
337 if (m_config.m_filePaths.contains(path))
338 {
339 LOG(VB_HTTP, LOG_INFO, LOC + QString("Removing path: '%1'").arg(path));
340 m_config.m_filePaths.removeOne(path);
341 }
342 }
344}
345
368{
369 bool newhandlers = false;
370 for (const auto & handler : std::as_const(Handlers))
371 {
372 if (ReservedPath(handler.first))
373 continue;
374 if (!std::any_of(m_config.m_handlers.cbegin(), m_config.m_handlers.cend(),
375 [&handler](const HTTPHandler& Handler) { return Handler.first == handler.first; }))
376 {
377 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding handler for '%1'").arg(handler.first));
378 m_config.m_handlers.push_back(handler);
379 newhandlers = true;
380 }
381 else
382 {
383 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Handler '%1' already registered - ignoring")
384 .arg(handler.first));
385 }
386 }
387 if (newhandlers)
389}
390
392{
393 bool stalehandlers = false;
394 for (const auto & handler : std::as_const(Handlers))
395 {
396 auto found = std::find_if(m_config.m_handlers.begin(), m_config.m_handlers.end(),
397 [&handler](const HTTPHandler& Handler) { return Handler.first == handler.first; });
398 if (found != m_config.m_handlers.end())
399 {
400 m_config.m_handlers.erase(found);
401 stalehandlers = true;
402 }
403 }
404 if (stalehandlers)
406}
407
409{
410 bool newservices = false;
411 for (const auto & service : std::as_const(Services))
412 {
413 if (ReservedPath(service.first))
414 continue;
415 if (!std::any_of(m_config.m_services.cbegin(), m_config.m_services.cend(),
416 [&service](const HTTPService& Service) { return Service.first == service.first; }))
417 {
418 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding service for '%1'").arg(service.first));
419 m_config.m_services.push_back(service);
420 newservices = true;
421 }
422 else
423 {
424 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Service '%1' already registered - ignoring")
425 .arg(service.first));
426 }
427 }
428 if (newservices)
430}
431
433{
434 bool staleservices = false;
435 for (const auto & service : std::as_const(Services))
436 {
437 auto found = std::find_if(m_config.m_services.begin(), m_config.m_services.end(),
438 [&service](const HTTPService& Service) { return Service.first == service.first; });
439 if (found != m_config.m_services.end())
440 {
441 m_config.m_services.erase(found);
442 staleservices = true;
443 }
444 }
445 if (staleservices)
447}
448
457{
458 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding error page handler"));
461}
462
464{
465 m_config.m_hosts.clear();
466
467 // Iterate over the addresses ServerPool was asked to listen on. This should
468 // pick up all variations of localhost and external IP etc
469 QStringList lookups;
470 auto defaults = DefaultListen();
471 bool allipv4 = false;
472 bool allipv6 = false;
473 for (const auto & address : std::as_const(defaults))
474 {
475 if (address == QHostAddress::AnyIPv4)
476 allipv4 |= true;
477 else if (address == QHostAddress::AnyIPv6)
478 allipv6 |= true;
479 else
480 lookups.append(address.toString());
481 }
482
483 // 'Any address' (0.0.0.0) is in use. Retrieve the complete list of avaible
484 // addresses and filter as required.
485 if (allipv4 || allipv6)
486 {
487 auto addresses = QNetworkInterface::allAddresses();
488 for (const auto & address : std::as_const(addresses))
489 {
490 if ((allipv4 && address.protocol() == QAbstractSocket::IPv4Protocol) ||
491 (allipv6 && address.protocol() == QAbstractSocket::IPv6Protocol))
492 {
493 lookups.append(address.toString());
494 }
495 }
496 }
497
498 lookups.removeDuplicates();
499
500 // Trigger reverse lookups
501 for (const auto & address : lookups)
502 {
504 QHostInfo::lookupHost(address, this, &MythHTTPServer::HostResolved);
505 }
506}
507
520{
522
523 // Add master backend. We need to resolve this separately to handle both status
524 // and SSL ports
526 QHostInfo::lookupHost(m_masterIPAddress, this, &MythHTTPServer::MasterResolved);
527
528 // Add configured overrides - are these still needed?
529 QStringList extras = gCoreContext->GetSetting("AllowedOriginsList", QString(
530 "https://chromecast.mythtv.org"
531 )).split(",");
532 for (const auto & extra : std::as_const(extras))
533 {
534 QString clean = extra.trimmed();
535 if (clean.startsWith("http://") || clean.startsWith("https://"))
536 m_config.m_allowedOrigins.append(clean);
537 }
538}
539
540QStringList MythHTTPServer::BuildAddressList(QHostInfo& Info)
541{
542 bool addhostname = true;
543 QString hostname = Info.hostName();
544 QStringList results;
545 auto ipaddresses = Info.addresses();
546 for(auto & address : ipaddresses)
547 {
548 QString result = MythHTTP::AddressToString(address);
549 // This filters out IPv6 addresses that are passed back as host names
550 if (result.contains(hostname))
551 addhostname = false;
552 results.append(result.toLower());
553 }
554 if (addhostname)
555 results.append(hostname.toLower());
556 return results;
557}
558
565{
566 auto addresses = BuildAddressList(Info);
567
568 // Add status and SSL addressed for each
569 for (const auto & address : std::as_const(addresses))
570 {
571 m_config.m_allowedOrigins.append(QString("http://%1").arg(address));
572 m_config.m_allowedOrigins.append(QString("http://%1:%2").arg(address).arg(m_masterStatusPort));
573 if (m_masterSSLPort != 0)
574 {
575 m_config.m_allowedOrigins.append(QString("https://%1").arg(address));
576 m_config.m_allowedOrigins.append(QString("https://%1:%2").arg(address).arg(m_masterSSLPort));
577 }
578 }
579 m_config.m_allowedOrigins.removeDuplicates();
580 if (--m_originLookups == 0)
581 DebugOrigins();
583}
584
586{
587 if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO))
588 {
589 LOG(VB_GENERAL, LOG_INFO, LOC +
590 QString("Name resolution complete: %1 'Origins' found").arg(m_config.m_allowedOrigins.size()));
591 for (const auto & address : std::as_const(m_config.m_allowedOrigins))
592 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Allowed origin: %1").arg(address));
593 }
594}
595
598void MythHTTPServer::ResolveHost(QHostInfo Info)
599{
600 auto addresses = BuildAddressList(Info);
601 for (const auto & address : std::as_const(addresses))
602 {
603 // The port is optional - so just add both to our list to simplify the
604 // checks when a request is received
605 m_config.m_hosts.append(QString("%1").arg(address));
606 if (m_config.m_port != 0)
607 m_config.m_hosts.append(QString("%1:%2").arg(address).arg(m_config.m_port));
608 if (m_config.m_sslPort != 0)
609 m_config.m_hosts.append(QString("%1:%2").arg(address).arg(m_config.m_sslPort));
610 }
611 m_config.m_hosts.removeDuplicates();
612 if (--m_hostLookups == 0)
613 DebugHosts();
615}
616
618{
619 if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO))
620 {
621 LOG(VB_GENERAL, LOG_INFO, LOC +
622 QString("Name resolution complete: %1 'Hosts' found").arg(m_config.m_hosts.size()));
623 for (const auto & address : std::as_const(m_config.m_hosts))
624 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Host: %1").arg(address));
625 }
626}
bool IsFrontend(void) const
is this process a frontend process
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
QString GetSetting(const QString &key, const QString &defaultval="")
int GetBackendStatusPort(void)
Returns the locally defined backend status port.
bool IsBackend(void) const
is this process a backend process
int GetMasterServerStatusPort(void)
Returns the Master Backend status port If no master server status port has been defined in the databa...
int GetNumSetting(const QString &key, int defaultval=0)
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
QSslConfiguration m_sslConfig
Definition: mythhttptypes.h:78
std::chrono::milliseconds m_timeout
Definition: mythhttptypes.h:69
QStringList m_filePaths
Definition: mythhttptypes.h:73
HTTPHandler m_errorPageHandler
Definition: mythhttptypes.h:76
HTTPHandlers m_handlers
Definition: mythhttptypes.h:74
quint16 m_port_2
Definition: mythhttptypes.h:65
QString m_rootDir
Definition: mythhttptypes.h:67
QStringList m_allowedOrigins
Definition: mythhttptypes.h:72
QStringList m_hosts
Definition: mythhttptypes.h:71
HTTPServices m_services
Definition: mythhttptypes.h:75
QString m_serverName
Definition: mythhttptypes.h:68
QString m_language
Definition: mythhttptypes.h:70
quint16 m_sslPort
Definition: mythhttptypes.h:66
static bool InitSSLServer(QSslConfiguration &Config)
Definition: mythhttps.cpp:13
void EnableDisable(bool Enable)
void RemoveServices(const HTTPServices &Services)
void NewErrorPageHandler(const HTTPHandler &Handler)
Add new error page handler.
~MythHTTPServer() override
void ServicesChanged(const HTTPServices &Services)
void MasterResolved(QHostInfo Info)
static bool ReservedPath(const QString &Path)
void Started(bool Tcp, bool Ssl)
void RemovePaths(const QStringList &Paths)
QQueue< MythTcpQueueEntry > m_connectionQueue
void Init()
Initialise server configuration.
void BuildOrigins()
Generate a list of allowed 'Origins' for validating CORS requests.
void StalePaths(const QStringList &Paths)
void StaleHandlers(const HTTPHandlers &Handlers)
void EnableHTTP(bool Enable)
void HandlersChanged(const HTTPHandlers &Handlers)
void StaleServices(const HTTPServices &Services)
void ErrorHandlerChanged(const HTTPHandler &Handler)
void AddHandlers(const HTTPHandlers &Handlers)
void OriginsChanged(const QStringList &Origins)
void AddServices(const HTTPServices &Services)
void AddErrorPageHandler(const HTTPHandler &Handler)
static QStringList BuildAddressList(QHostInfo &Info)
MythHTTPConfig m_config
void newTcpConnection(qintptr socket) override
void NewPaths(const QStringList &Paths)
Add new paths that will serve simple files.
void ResolveMaster(QHostInfo Info)
Add master backend addresses to the allowed Origins list.
void RemoveHandlers(const HTTPHandlers &Handlers)
void NewServices(const HTTPServices &Services)
void PathsChanged(const QStringList &Paths)
void ResolveHost(QHostInfo Info)
Add the results of a reverse lookup to our allowed list.
void ProcessTCPQueueHandler()
void AddPaths(const QStringList &Paths)
void HostResolved(QHostInfo Info)
QString m_masterIPAddress
void HostsChanged(const QStringList &Hosts)
void ProcessTCPQueue()
void NewHandlers(const HTTPHandlers &Handlers)
Add new handlers.
size_t MaxThreads() const
size_t AvailableThreads() const
void AddThread(MythHTTPThread *Thread)
static QString AddressToString(QHostAddress &Address)
static QList< QHostAddress > DefaultListen(void)
Definition: serverpool.cpp:305
bool listen(QList< QHostAddress > addrs, quint16 port, bool requireall=true, PoolServerType type=kTCPServer)
Definition: serverpool.cpp:395
void close(void)
Definition: serverpool.cpp:374
bool isListening(void) const
Definition: serverpool.h:92
quint16 serverPort(void) const
Definition: serverpool.h:95
QString GetValue(const QString &setting)
unsigned short uint16_t
Definition: iso6937tables.h:3
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetShareDir(void)
Definition: mythdirs.cpp:283
#define LOC
std::pair< QString, HTTPServiceCtor > HTTPService
Definition: mythhttptypes.h:54
std::vector< HTTPHandler > HTTPHandlers
Definition: mythhttptypes.h:48
std::vector< HTTPService > HTTPServices
Definition: mythhttptypes.h:55
std::pair< QString, HTTPFunction > HTTPHandler
Definition: mythhttptypes.h:47
#define HTTP_SERVICES_DIR
Definition: mythhttptypes.h:26
#define HTTP_SOCKET_TIMEOUT_MS
Definition: mythhttptypes.h:25
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
const char * GetMythSourceVersion()
Definition: mythversion.cpp:7
string version
Definition: giantbomb.py:185
string hostname
Definition: caa.py:17
@ kSSLServer
Definition: serverpool.h:33