MythTV master
mythhttpserver.cpp
Go to the documentation of this file.
1// Qt
2#include <QDirIterator>
3#include <QNetworkInterface>
4#include <QCoreApplication>
5#include <QSslKey>
6#include <QSslCipher>
7#include <QSslCertificate>
8
9// MythTV
10#include "mythversion.h"
11#include "mythdirs.h"
12#include "mythcorecontext.h"
13#include "mythlogging.h"
15#if CONFIG_LIBDNS_SD
16#include "bonjourregister.h"
17#endif
18#include "http/mythhttpsocket.h"
20#include "http/mythhttpthread.h"
21#include "http/mythhttps.h"
22#include "http/mythhttpserver.h"
23
24// Std
25#ifndef _WIN32
26#include <sys/utsname.h>
27#endif
28
29#define LOC QString("HTTPServer: ")
30
32{
33 // Join the dots
45
46 // Find our static content
48 while (m_config.m_rootDir.endsWith("/"))
49 m_config.m_rootDir.chop(1);
50 m_config.m_rootDir.append(QStringLiteral("/html"));
51
52 // Add our default paths (mostly static js, css, images etc).
53 // We need to pass individual directories to the threads, so inspect the
54 // the paths we want for sub-directories
55 static const QStringList s_dirs = { "/assets/", "/3rdParty/", "/css/", "/images/", "/js/", "/misc/", "/apps/", "/xslt/" };
56 m_config.m_filePaths.clear();
57 QStringList dirs;
58 for (const auto & dir : s_dirs)
59 {
60 dirs.append(dir);
61 QDirIterator it(m_config.m_rootDir + dir, QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
62 while (it.hasNext())
63 dirs.append(it.next().remove(m_config.m_rootDir) + "/");
64 }
65
66 // And finally the root handler
67 dirs.append("/");
68 NewPaths(dirs);
69}
70
72{
73 Stopped();
74}
75
77{
78 if (Enable && !isListening())
79 {
80 Init();
81 bool tcp = m_config.m_port != 0;
82 bool ssl = m_config.m_sslPort != 0;
83
84 if (tcp)
85 {
86 tcp = listen(m_config.m_port);
87 // ServerPool as written will overwrite the port setting if we listen
88 // on an additional port (i.e. SSL). So check which port is in use before
89 // continuing
90 if (m_config.m_port != serverPort())
91 {
92 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Server is using port %1 - expected %2")
93 .arg(serverPort()).arg(m_config.m_port));
95 }
98 }
99
100 if (ssl)
101 {
102 ssl = listen(m_config.m_sslPort, true, kSSLServer);
104 {
105 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Server is using port %1 - expected %2")
106 .arg(serverPort()).arg(m_config.m_sslPort));
108 }
109 }
110
111 Started(tcp, ssl);
112 }
113 else if (!Enable && isListening())
114 {
115 close();
116 Stopped();
117 }
118}
119
126{
127 // Just in case we get in a mess
128 Stopped();
129
130 // Decide on the ports to use
132 {
133 m_config.m_port = XmlConfiguration().GetValue("UPnP/MythFrontend/ServicePort", 6547);
134 // I don't think there is an existing setting for this
135 m_config.m_sslPort = static_cast<uint16_t>(gCoreContext->GetNumSetting("FrontendSSLPort", m_config.m_port + 10));
136
137 }
138 else if (gCoreContext->IsBackend())
139 {
141 // Additional port, may be removed later
143 m_config.m_sslPort = static_cast<uint16_t>(gCoreContext->GetNumSetting("BackendSSLPort", m_config.m_port + 10));
144 }
145 else
146 {
147 // N.B. This assumes only the frontend and backend use HTTP...
148 m_config.m_port = 0;
150 }
151
152 // If this fails, unset the SSL port
153#ifndef QT_NO_OPENSSL
155#endif
156 {
158 }
159
160 if (m_config.m_sslPort == 0)
161 LOG(VB_HTTP, LOG_INFO, LOC + "SSL is disabled");
162
163 // Set the server ident
164 QString version = GetMythSourceVersion();
165 if (version.startsWith("v"))
166 version = version.right(version.length() - 1);
167
168#ifdef _WIN32
169 QString server = QStringLiteral("Windows");
170#else
171 struct utsname uname_info {};
172 uname(&uname_info);
173 QString server = QStringLiteral("%1/%2").arg(uname_info.sysname, uname_info.release);
174#endif
175 m_config.m_serverName = QString("MythTV/%1 %2 UPnP/1.0").arg(version, server);
176
177 // Retrieve language
179
180 // Get master backend details for Origin checks
184
185 // Get keep alive timeout
186 auto timeout = gCoreContext->GetNumSetting("HTTP/KeepAliveTimeoutSecs", HTTP_SOCKET_TIMEOUT_MS / 1000);
187 m_config.m_timeout = static_cast<std::chrono::milliseconds>(timeout * 1000);
188}
189
190void MythHTTPServer::Started([[maybe_unused]] bool Tcp,
191 [[maybe_unused]] bool Ssl)
192{
193#if CONFIG_LIBDNS_SD
194 // Advertise our webserver
195 delete m_bonjour;
196 delete m_bonjourSSL;
197 m_bonjour = nullptr;
198 m_bonjourSSL = nullptr;
199 if (!(Tcp || Ssl))
200 return;
201
202 auto host = QHostInfo::localHostName();
203 if (host.isEmpty())
204 host = tr("Unknown");
205
206 if (Tcp)
207 {
208 m_bonjour = new BonjourRegister();
209 m_bonjour->Register(m_config.m_port, QByteArrayLiteral("_http._tcp"),
210 QStringLiteral("%1 on %2").arg(QCoreApplication::applicationName(), host).toLatin1().constData(), {});
211 }
212
213 if (Ssl)
214 {
215 m_bonjourSSL = new BonjourRegister();
216 m_bonjourSSL->Register(m_config.m_sslPort, QByteArrayLiteral("_https._tcp"),
217 QStringLiteral("%1 on %2").arg(QCoreApplication::applicationName(), host).toLatin1().constData(), {});
218 }
219#endif
220
221 // Build our list of hosts and allowed origins.
222 BuildHosts();
223 BuildOrigins();
224}
225
227{
228 // Clear allowed origins
229 m_config.m_hosts.clear();
231
232#if CONFIG_LIBDNS_SD
233 // Stop advertising
234 delete m_bonjour;
235 delete m_bonjourSSL;
236 m_bonjour = nullptr;
237 m_bonjourSSL = nullptr;
238#endif
239}
240
242{
243 if (!m_connectionQueue.empty())
244 {
245 emit ProcessTCPQueue();
246 }
247}
248
250{
251 if (AvailableThreads() > 0)
252 {
253 auto entry = m_connectionQueue.dequeue();
255 auto name = QString("HTTP%1%2").arg(entry.m_ssl ? "S" : "").arg(m_threadNum++);
256 auto * newthread = new MythHTTPThread(this, m_config, name, entry.m_socketFD, entry.m_ssl);
257 AddThread(newthread);
258 connect(newthread->qthread(), &QThread::finished, this, &MythHTTPThreadPool::ThreadFinished);
259 connect(newthread->qthread(), &QThread::finished, this, &MythHTTPServer::ThreadFinished);
260 newthread->start();
261 return;
262 }
263}
264
266{
267 if (!Socket)
268 return;
269 auto * server = qobject_cast<PrivTcpServer*>(QObject::sender());
270 MythTcpQueueEntry entry;
271 entry.m_socketFD = Socket;
272 entry.m_ssl = (server->GetServerType() == kSSLServer);
273 m_connectionQueue.enqueue(entry);
274 emit ProcessTCPQueue();
275}
276
277bool MythHTTPServer::ReservedPath(const QString& Path)
278{
279 static const QStringList s_reservedPaths { HTTP_SERVICES_DIR };
280 if (s_reservedPaths.contains(Path, Qt::CaseInsensitive))
281 {
282 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Server path '%1' is reserved - ignoring").arg(Path));
283 return true;
284 }
285 return false;
286}
287
309void MythHTTPServer::NewPaths(const QStringList &Paths)
310{
311 if (Paths.isEmpty())
312 return;
313 for (const auto & path : std::as_const(Paths))
314 {
315 if (ReservedPath(path))
316 continue;
317 if (m_config.m_filePaths.contains(path))
318 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("'%1' is already registered").arg(path));
319 else
320 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding path: '%1'").arg(path));
321 m_config.m_filePaths.append(path);
322 }
324}
325
326void MythHTTPServer::StalePaths(const QStringList& Paths)
327{
328 if (Paths.isEmpty())
329 return;
330 for (const auto & path : std::as_const(Paths))
331 {
332 if (m_config.m_filePaths.contains(path))
333 {
334 LOG(VB_HTTP, LOG_INFO, LOC + QString("Removing path: '%1'").arg(path));
335 m_config.m_filePaths.removeOne(path);
336 }
337 }
339}
340
363{
364 bool newhandlers = false;
365 for (const auto & handler : std::as_const(Handlers))
366 {
367 if (ReservedPath(handler.first))
368 continue;
369 if (!std::any_of(m_config.m_handlers.cbegin(), m_config.m_handlers.cend(),
370 [&handler](const HTTPHandler& Handler) { return Handler.first == handler.first; }))
371 {
372 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding handler for '%1'").arg(handler.first));
373 m_config.m_handlers.push_back(handler);
374 newhandlers = true;
375 }
376 else
377 {
378 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Handler '%1' already registered - ignoring")
379 .arg(handler.first));
380 }
381 }
382 if (newhandlers)
384}
385
387{
388 bool stalehandlers = false;
389 for (const auto & handler : std::as_const(Handlers))
390 {
391 auto found = std::find_if(m_config.m_handlers.begin(), m_config.m_handlers.end(),
392 [&handler](const HTTPHandler& Handler) { return Handler.first == handler.first; });
393 if (found != m_config.m_handlers.end())
394 {
395 m_config.m_handlers.erase(found);
396 stalehandlers = true;
397 }
398 }
399 if (stalehandlers)
401}
402
404{
405 bool newservices = false;
406 for (const auto & service : std::as_const(Services))
407 {
408 if (ReservedPath(service.first))
409 continue;
410 if (!std::any_of(m_config.m_services.cbegin(), m_config.m_services.cend(),
411 [&service](const HTTPService& Service) { return Service.first == service.first; }))
412 {
413 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding service for '%1'").arg(service.first));
414 m_config.m_services.push_back(service);
415 newservices = true;
416 }
417 else
418 {
419 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Service '%1' already registered - ignoring")
420 .arg(service.first));
421 }
422 }
423 if (newservices)
425}
426
428{
429 bool staleservices = false;
430 for (const auto & service : std::as_const(Services))
431 {
432 auto found = std::find_if(m_config.m_services.begin(), m_config.m_services.end(),
433 [&service](const HTTPService& Service) { return Service.first == service.first; });
434 if (found != m_config.m_services.end())
435 {
436 m_config.m_services.erase(found);
437 staleservices = true;
438 }
439 }
440 if (staleservices)
442}
443
452{
453 LOG(VB_HTTP, LOG_INFO, LOC + QString("Adding error page handler"));
456}
457
459{
460 m_config.m_hosts.clear();
461
462 // Iterate over the addresses ServerPool was asked to listen on. This should
463 // pick up all variations of localhost and external IP etc
464 QStringList lookups;
465 auto defaults = DefaultListen();
466 bool allipv4 = false;
467 bool allipv6 = false;
468 for (const auto & address : std::as_const(defaults))
469 {
470 if (address == QHostAddress::AnyIPv4)
471 allipv4 |= true;
472 else if (address == QHostAddress::AnyIPv6)
473 allipv6 |= true;
474 else
475 lookups.append(address.toString());
476 }
477
478 // 'Any address' (0.0.0.0) is in use. Retrieve the complete list of avaible
479 // addresses and filter as required.
480 if (allipv4 || allipv6)
481 {
482 auto addresses = QNetworkInterface::allAddresses();
483 for (const auto & address : std::as_const(addresses))
484 {
485 if ((allipv4 && address.protocol() == QAbstractSocket::IPv4Protocol) ||
486 (allipv6 && address.protocol() == QAbstractSocket::IPv6Protocol))
487 {
488 lookups.append(address.toString());
489 }
490 }
491 }
492
493 lookups.removeDuplicates();
494
495 // Trigger reverse lookups
496 for (const auto & address : lookups)
497 {
499 QHostInfo::lookupHost(address, this, &MythHTTPServer::HostResolved);
500 }
501}
502
515{
517
518 // Add master backend. We need to resolve this separately to handle both status
519 // and SSL ports
521 QHostInfo::lookupHost(m_masterIPAddress, this, &MythHTTPServer::MasterResolved);
522
523 // Add configured overrides - are these still needed?
524 QStringList extras = gCoreContext->GetSetting("AllowedOriginsList", QString(
525 "https://chromecast.mythtv.org"
526 )).split(",");
527 for (const auto & extra : std::as_const(extras))
528 {
529 QString clean = extra.trimmed();
530 if (clean.startsWith("http://") || clean.startsWith("https://"))
531 m_config.m_allowedOrigins.append(clean);
532 }
533}
534
535QStringList MythHTTPServer::BuildAddressList(QHostInfo& Info)
536{
537 bool addhostname = true;
538 QString hostname = Info.hostName();
539 QStringList results;
540 auto ipaddresses = Info.addresses();
541 for(auto & address : ipaddresses)
542 {
543 QString result = MythHTTP::AddressToString(address);
544 // This filters out IPv6 addresses that are passed back as host names
545 if (result.contains(hostname))
546 addhostname = false;
547 results.append(result.toLower());
548 }
549 if (addhostname)
550 results.append(hostname.toLower());
551 return results;
552}
553
560{
561 auto addresses = BuildAddressList(Info);
562
563 // Add status and SSL addressed for each
564 for (const auto & address : std::as_const(addresses))
565 {
566 m_config.m_allowedOrigins.append(QString("http://%1").arg(address));
567 m_config.m_allowedOrigins.append(QString("http://%1:%2").arg(address).arg(m_masterStatusPort));
568 if (m_masterSSLPort != 0)
569 {
570 m_config.m_allowedOrigins.append(QString("https://%1").arg(address));
571 m_config.m_allowedOrigins.append(QString("https://%1:%2").arg(address).arg(m_masterSSLPort));
572 }
573 }
574 m_config.m_allowedOrigins.removeDuplicates();
575 if (--m_originLookups == 0)
576 DebugOrigins();
578}
579
581{
582 if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO))
583 {
584 LOG(VB_GENERAL, LOG_INFO, LOC +
585 QString("Name resolution complete: %1 'Origins' found").arg(m_config.m_allowedOrigins.size()));
586 for (const auto & address : std::as_const(m_config.m_allowedOrigins))
587 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Allowed origin: %1").arg(address));
588 }
589}
590
593void MythHTTPServer::ResolveHost(QHostInfo Info)
594{
595 auto addresses = BuildAddressList(Info);
596 for (const auto & address : std::as_const(addresses))
597 {
598 // The port is optional - so just add both to our list to simplify the
599 // checks when a request is received
600 m_config.m_hosts.append(QString("%1").arg(address));
601 if (m_config.m_port != 0)
602 m_config.m_hosts.append(QString("%1:%2").arg(address).arg(m_config.m_port));
603 if (m_config.m_sslPort != 0)
604 m_config.m_hosts.append(QString("%1:%2").arg(address).arg(m_config.m_sslPort));
605 }
606 m_config.m_hosts.removeDuplicates();
607 if (--m_hostLookups == 0)
608 DebugHosts();
610}
611
613{
614 if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO))
615 {
616 LOG(VB_GENERAL, LOG_INFO, LOC +
617 QString("Name resolution complete: %1 'Hosts' found").arg(m_config.m_hosts.size()));
618 for (const auto & address : std::as_const(m_config.m_hosts))
619 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Host: %1").arg(address));
620 }
621}
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:261
#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