23#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
24#include <QtSystemDetection>
28#include <sys/utsname.h>
32#include <QSslConfiguration>
35#include <QSslCertificate>
45#include "libmythbase/mythversion.h"
62 QStringList allowedMethods;
64 allowedMethods.append(
"GET");
66 allowedMethods.append(
"HEAD");
68 allowedMethods.append(
"POST");
76 allowedMethods.append(
"OPTIONS");
80 allowedMethods.append(
"M-SEARCH");
82 allowedMethods.append(
"SUBSCRIBE");
84 allowedMethods.append(
"UNSUBSCRIBE");
86 allowedMethods.append(
"NOTIFY");
88 if (!allowedMethods.isEmpty())
94 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServerExtension::ProcessOptions(): "
95 "Error: No methods supported for "
96 "extension - %1").arg(
m_sName));
119 m_threadPool(
"HttpServerPool"),
120 m_privateToken(QUuid::createUuid().
toString())
123 int maxHttpWorkers = std::max(QThread::idealThreadCount() * 2, 2);
129 LOG(VB_HTTP, LOG_NOTICE, QString(
"HttpServer(): Max Thread Count %1")
139 .arg(LOBYTE(LOWORD(GetVersion())))
140 .arg(HIBYTE(LOWORD(GetVersion())));
142 struct utsname uname_info {};
143 uname( &uname_info );
145 .arg(uname_info.sysname, uname_info.release);
149 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);
207 if (hostKeyPath.isEmpty())
210 QFile hostKeyFile(hostKeyPath);
211 if (!hostKeyFile.exists() || !hostKeyFile.open(QIODevice::ReadOnly))
213 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: SSL Host key file (%1) does not exist or is not readable").arg(hostKeyPath));
218 QByteArray rawHostKey = hostKeyFile.readAll();
219 QSslKey hostKey = QSslKey(rawHostKey, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
220 if (!hostKey.isNull())
224 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load host key from file (%1)").arg(hostKeyPath));
229 QSslCertificate hostCert;
230 QList<QSslCertificate> certList = QSslCertificate::fromPath(hostCertPath);
231 if (!certList.isEmpty())
232 hostCert = certList.first();
234 if (!hostCert.isNull())
236 if (hostCert.effectiveDate() > QDateTime::currentDateTime())
238 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Host certificate start date in future (%1)").arg(hostCertPath));
242 if (hostCert.expiryDate() < QDateTime::currentDateTime())
244 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Host certificate has expired (%1)").arg(hostCertPath));
252 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load host cert from file (%1)").arg(hostCertPath));
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()."));
426 timeout = list.first()->GetSocketTimeout();
446 ,
const QSslConfiguration& sslConfig
449 : m_httpServer(httpServer), m_socket(sock),
450 m_socketTimeout(5s), m_connectionType(
type)
452 , m_sslConfig(sslConfig)
455 LOG(VB_HTTP, LOG_INFO, QString(
"HttpWorker(%1): New connection")
465 LOG(VB_HTTP, LOG_DEBUG,
466 QString(
"HttpWorker::run() socket=%1 -- begin").arg(
m_socket));
468 bool bKeepAlive =
true;
470 QTcpSocket *pSocket =
nullptr;
471 bool bEncrypted =
false;
473 static constexpr std::chrono::milliseconds k_poll_interval {1ms};
479 auto *pSslSocket =
new QSslSocket();
480 if (pSslSocket->setSocketDescriptor(
m_socket)
484 pSslSocket->startServerEncryption();
485 attempt_time.
start();
488 pSslSocket->waitForEncrypted(k_poll_interval.count());
490 if (pSslSocket->isEncrypted())
492 LOG(VB_HTTP, LOG_INFO,
"SSL Handshake occurred, connection encrypted");
493 LOG(VB_HTTP, LOG_INFO, QString(
"Using %1 cipher").arg(pSslSocket->sessionCipher().name()));
498 LOG(VB_HTTP, LOG_WARNING,
"SSL Handshake FAILED, connection terminated");
500 pSslSocket =
nullptr;
506 pSslSocket =
nullptr;
510 pSocket = pSslSocket;
519 pSocket =
new QTcpSocket();
520 pSocket->setSocketDescriptor(
m_socket);
530 pSocket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
531 int nRequestsHandled = 0;
536 pSocket->state() == QAbstractSocket::ConnectedState)
542 attempt_time.
start();
544 && pSocket->state() == QAbstractSocket::ConnectedState
545 && pSocket->bytesAvailable() <= 0
549 pSocket->waitForReadyRead(k_poll_interval.count());
561 if (pRequest !=
nullptr)
587 LOG(VB_HTTP, LOG_ERR,
"ParseRequest Failed.");
600 LOG(VB_HTTP, LOG_ERR,
601 QString(
"socket(%1) - Error returned from "
602 "SendResponse... Closing connection")
603 .arg(pSocket->socketDescriptor()));
617 LOG(VB_GENERAL, LOG_ERR,
618 "Error Creating BufferedSocketDeviceRequest");
626 LOG(VB_GENERAL, LOG_ERR,
627 "HttpWorkerThread::ProcessWork - Unexpected Exception.");
632 if ((pSocket->error() != QAbstractSocket::UnknownSocketError) &&
633 (!bKeepAlive || pSocket->error() != QAbstractSocket::SocketTimeoutError))
635 LOG(VB_HTTP, LOG_WARNING, QString(
"HttpWorker(%1): Error %2 (%3)")
637 .arg(pSocket->errorString())
638 .arg(pSocket->error()));
641 std::chrono::milliseconds writeTimeout = 5s;
643 if (pSocket->bytesToWrite() > 0)
645 LOG(VB_HTTP, LOG_DEBUG,
646 QString(
"HttpWorker(%1): Waiting for %2 bytes to be written before closing the connection.")
647 .arg(
m_socket).arg(pSocket->bytesToWrite())
650 attempt_time.
start();
652 pSocket->isValid() &&
653 pSocket->state() == QAbstractSocket::ConnectedState &&
654 pSocket->bytesToWrite() > 0 &&
655 attempt_time.
elapsed() < writeTimeout
666 pSocket->waitForBytesWritten(k_poll_interval.count());
669 if (pSocket->bytesToWrite() > 0)
671 LOG(VB_HTTP, LOG_WARNING, QString(
"HttpWorker(%1): "
672 "Failed to write %2 bytes to "
675 .arg(pSocket->bytesToWrite())
676 .arg(pSocket->errorString()));
679 LOG(VB_HTTP, LOG_INFO, QString(
"HttpWorker(%1): Connection %2 closed. %3 requests were handled")
681 .arg(pSocket->socketDescriptor())
682 .arg(nRequestsHandled));
688 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.