23#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
24#include <QtSystemDetection>
28#include <sys/utsname.h>
32#include <QSslConfiguration>
35#include <QSslCertificate>
45#include "libmythbase/mythversion.h"
61 QStringList allowedMethods;
63 allowedMethods.append(
"GET");
65 allowedMethods.append(
"HEAD");
67 allowedMethods.append(
"POST");
75 allowedMethods.append(
"OPTIONS");
79 allowedMethods.append(
"M-SEARCH");
81 allowedMethods.append(
"SUBSCRIBE");
83 allowedMethods.append(
"UNSUBSCRIBE");
85 allowedMethods.append(
"NOTIFY");
87 if (!allowedMethods.isEmpty())
93 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServerExtension::ProcessOptions(): "
94 "Error: No methods supported for "
95 "extension - %1").arg(
m_sName));
118 m_threadPool(
"HttpServerPool"),
119 m_privateToken(QUuid::createUuid().
toString())
122 int maxHttpWorkers = std::max(QThread::idealThreadCount() * 2, 2);
128 LOG(VB_HTTP, LOG_NOTICE, QString(
"HttpServer(): Max Thread Count %1")
138 .arg(LOBYTE(LOWORD(GetVersion())))
139 .arg(HIBYTE(LOWORD(GetVersion())));
141 struct utsname uname_info {};
142 uname( &uname_info );
144 .arg(uname_info.sysname, uname_info.release);
148 LOG(VB_HTTP, LOG_INFO, QString(
"HttpServer() - SharePath = %1")
179 m_sslConfig = QSslConfiguration::defaultConfiguration();
182 m_sslConfig.setSslOption(QSsl::SslOptionDisableLegacyRenegotiation,
true);
183 m_sslConfig.setSslOption(QSsl::SslOptionDisableCompression,
true);
185 QList<QSslCipher> availableCiphers = QSslConfiguration::supportedCiphers();
186 QList<QSslCipher> secureCiphers;
187 QList<QSslCipher>::iterator it;
188 for (it = availableCiphers.begin(); it != availableCiphers.end(); ++it)
191 if ((*it).usedBits() < 128)
194 if ((*it).name().startsWith(
"RC4") ||
195 (*it).name().startsWith(
"EXP") ||
196 (*it).name().startsWith(
"ADH") ||
197 (*it).name().contains(
"NULL"))
200 secureCiphers.append(*it);
206 if (hostKeyPath.isEmpty())
210 QSslCertificate hostCert;
211 QList<QSslCertificate> certList = QSslCertificate::fromPath(hostCertPath);
212 if (!certList.isEmpty())
213 hostCert = certList.first();
215 if (!hostCert.isNull())
217 if (hostCert.effectiveDate() > QDateTime::currentDateTime())
219 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Host certificate start date in future (%1)").arg(hostCertPath));
223 if (hostCert.expiryDate() < QDateTime::currentDateTime())
225 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Host certificate has expired (%1)").arg(hostCertPath));
233 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load host cert from file (%1)").arg(hostCertPath));
237 QFile hostKeyFile(hostKeyPath);
238 if (!hostKeyFile.exists() || !hostKeyFile.open(QIODevice::ReadOnly))
240 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: SSL Host key file (%1) does not exist or is not readable").arg(hostKeyPath));
244 QByteArray rawHostKey = hostKeyFile.readAll();
245 QSslKey hostKey = QSslKey(rawHostKey, hostCert.publicKey().algorithm(), QSsl::Pem, QSsl::PrivateKey);
246 if (!hostKey.isNull())
252 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load host key from file (%1)").arg(hostKeyPath));
257 QList< QSslCertificate > CACertList = QSslCertificate::fromPath(caCertPath);
259 if (!CACertList.isEmpty())
261 else if (!caCertPath.isEmpty())
262 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load CA cert file (%1)").arg(caCertPath));
283 if (mythVersion.startsWith(
"v"))
284 mythVersion = mythVersion.right(mythVersion.length() - 1);
296 auto *server = qobject_cast<PrivTcpServer *>(QObject::sender());
298 type = server->GetServerType();
306 QString(
"HttpServer%1").arg(socket));
315 if (pExtension !=
nullptr )
317 LOG(VB_HTTP, LOG_INFO, QString(
"HttpServer: Registering %1 extension").arg(pExtension->
m_sName));
325 for(
const QString& base : std::as_const(list))
328 LOG(VB_HTTP, LOG_INFO, QString(
"HttpServer: Registering %1 extension path %2")
329 .arg(pExtension->
m_sName, base));
341 if (pExtension !=
nullptr )
347 for(
const QString& base : std::as_const(list))
364 bool bProcessed =
false;
366 LOG(VB_HTTP, LOG_DEBUG, QString(
"m_sBaseUrl: %1").arg( pRequest->
m_sBaseUrl ));
371 for (
int nIdx=0; nIdx < list.size() && !bProcessed; nIdx++ )
376 bProcessed = list[ nIdx ]->ProcessOptions(pRequest);
378 bProcessed = list[ nIdx ]->ProcessRequest(pRequest);
382 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer::DelegateRequest - "
383 "Unexpected Exception - "
384 "pExtension->ProcessRequest()."));
395 bProcessed = ext->ProcessOptions(pRequest);
397 bProcessed = ext->ProcessRequest(pRequest);
401 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer::DelegateRequest - "
402 "Unexpected Exception - "
403 "pExtension->ProcessRequest()."));
423 timeout = list.first()->GetSocketTimeout();
443 ,
const QSslConfiguration& sslConfig
446 : m_httpServer(httpServer), m_socket(sock),
447 m_socketTimeout(5s), m_connectionType(
type)
449 , m_sslConfig(sslConfig)
452 LOG(VB_HTTP, LOG_INFO, QString(
"HttpWorker(%1): New connection")
462 LOG(VB_HTTP, LOG_DEBUG,
463 QString(
"HttpWorker::run() socket=%1 -- begin").arg(
m_socket));
465 bool bKeepAlive =
true;
467 QTcpSocket *pSocket =
nullptr;
468 bool bEncrypted =
false;
470 static constexpr std::chrono::milliseconds k_poll_interval {1ms};
476 auto *pSslSocket =
new QSslSocket();
477 if (pSslSocket->setSocketDescriptor(
m_socket)
481 pSslSocket->startServerEncryption();
482 attempt_time.
start();
485 pSslSocket->waitForEncrypted(k_poll_interval.count());
487 if (pSslSocket->isEncrypted())
489 LOG(VB_HTTP, LOG_INFO,
"SSL Handshake occurred, connection encrypted");
490 LOG(VB_HTTP, LOG_INFO, QString(
"Using %1 cipher").arg(pSslSocket->sessionCipher().name()));
495 LOG(VB_HTTP, LOG_WARNING,
"SSL Handshake FAILED, connection terminated");
497 pSslSocket =
nullptr;
503 pSslSocket =
nullptr;
507 pSocket = pSslSocket;
516 pSocket =
new QTcpSocket();
517 pSocket->setSocketDescriptor(
m_socket);
527 pSocket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
528 int nRequestsHandled = 0;
533 pSocket->state() == QAbstractSocket::ConnectedState)
539 attempt_time.
start();
541 && pSocket->state() == QAbstractSocket::ConnectedState
542 && pSocket->bytesAvailable() <= 0
546 pSocket->waitForReadyRead(k_poll_interval.count());
558 if (pRequest !=
nullptr)
584 LOG(VB_HTTP, LOG_ERR,
"ParseRequest Failed.");
597 LOG(VB_HTTP, LOG_ERR,
598 QString(
"socket(%1) - Error returned from "
599 "SendResponse... Closing connection")
600 .arg(pSocket->socketDescriptor()));
614 LOG(VB_GENERAL, LOG_ERR,
615 "Error Creating BufferedSocketDeviceRequest");
623 LOG(VB_GENERAL, LOG_ERR,
624 "HttpWorkerThread::ProcessWork - Unexpected Exception.");
629 if ((pSocket->error() != QAbstractSocket::UnknownSocketError) &&
630 (!bKeepAlive || pSocket->error() != QAbstractSocket::SocketTimeoutError))
632 LOG(VB_HTTP, LOG_WARNING, QString(
"HttpWorker(%1): Error %2 (%3)")
634 .arg(pSocket->errorString())
635 .arg(pSocket->error()));
638 std::chrono::milliseconds writeTimeout = 5s;
640 if (pSocket->bytesToWrite() > 0)
642 LOG(VB_HTTP, LOG_DEBUG,
643 QString(
"HttpWorker(%1): Waiting for %2 bytes to be written before closing the connection.")
644 .arg(
m_socket).arg(pSocket->bytesToWrite())
647 attempt_time.
start();
649 pSocket->isValid() &&
650 pSocket->state() == QAbstractSocket::ConnectedState &&
651 pSocket->bytesToWrite() > 0 &&
652 attempt_time.
elapsed() < writeTimeout
663 pSocket->waitForBytesWritten(k_poll_interval.count());
666 if (pSocket->bytesToWrite() > 0)
668 LOG(VB_HTTP, LOG_WARNING, QString(
"HttpWorker(%1): "
669 "Failed to write %2 bytes to "
672 .arg(pSocket->bytesToWrite())
673 .arg(pSocket->errorString()));
676 LOG(VB_HTTP, LOG_INFO, QString(
"HttpWorker(%1): Connection %2 closed. %3 requests were handled")
678 .arg(pSocket->socketDescriptor())
679 .arg(nRequestsHandled));
685 LOG(VB_HTTP, LOG_DEBUG, QString(
"HttpWorker::run() socket=%1 -- end").arg(
m_socket));
QByteArray GetResponsePage(void)
HttpResponseType m_eResponseType
qint64 SendResponse(void)
void SetKeepAliveTimeout(std::chrono::seconds nTimeout)
IPostProcess * m_pPostProcess
bool GetKeepAlive() const
void SetResponseHeader(const QString &sKey, const QString &sValue, bool replace=false)
virtual QStringList GetBasePaths()=0
virtual bool ProcessOptions(HTTPRequest *pRequest)
Handle an OPTIONS request.
QMultiMap< QString, HttpServerExtension * > m_basePaths
uint GetSocketTimeout(HTTPRequest *pRequest) const
Get the idle socket timeout value for the relevant extension.
void DelegateRequest(HTTPRequest *pRequest)
static QMutex s_platformLock
static QString GetServerVersion(void)
void UnregisterExtension(HttpServerExtension *pExtension)
void newTcpConnection(qintptr socket) override
bool IsRunning(void) const
HttpServerExtensionList m_extensions
static QString s_platform
static QString GetPlatform(void)
QSslConfiguration m_sslConfig
void RegisterExtension(HttpServerExtension *pExtension)
HttpWorker(HttpServer &httpServer, qintptr sock, PoolServerType type, const QSslConfiguration &sslConfig)
HttpServer & m_httpServer
PoolServerType m_connectionType
std::chrono::milliseconds m_socketTimeout
QSslConfiguration m_sslConfig
virtual void ExecutePostProcess()=0
int maxThreadCount(void) const
void setMaxThreadCount(int maxThreadCount)
void startReserved(QRunnable *runnable, const QString &debugName, std::chrono::milliseconds waitForAvailMS=0ms)
QString GetSetting(const QString &key, const QString &defaultval="")
bool CheckSubnet(const QAbstractSocket *socket)
Check if a socket is connected to an approved peer.
int GetNumSetting(const QString &key, int defaultval=0)
A QElapsedTimer based timer to replace use of QTime as a timer.
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
void start(void)
starts measuring elapsed time.
void setMaxPendingConnections(int n)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetShareDir(void)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
const char * GetMythSourceVersion()
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.