24#include <sys/utsname.h>
28#include <QSslConfiguration>
31#include <QSslCertificate>
41#include "libmythbase/mythversion.h"
58 QStringList allowedMethods;
60 allowedMethods.append(
"GET");
62 allowedMethods.append(
"HEAD");
64 allowedMethods.append(
"POST");
72 allowedMethods.append(
"OPTIONS");
76 allowedMethods.append(
"M-SEARCH");
78 allowedMethods.append(
"SUBSCRIBE");
80 allowedMethods.append(
"UNSUBSCRIBE");
82 allowedMethods.append(
"NOTIFY");
84 if (!allowedMethods.isEmpty())
90 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServerExtension::ProcessOptions(): "
91 "Error: No methods supported for "
92 "extension - %1").arg(
m_sName));
115 m_threadPool(
"HttpServerPool"),
116 m_privateToken(QUuid::createUuid().
toString())
119 int maxHttpWorkers = std::max(QThread::idealThreadCount() * 2, 2);
125 LOG(VB_HTTP, LOG_NOTICE, QString(
"HttpServer(): Max Thread Count %1")
135 .arg(LOBYTE(LOWORD(GetVersion())))
136 .arg(HIBYTE(LOWORD(GetVersion())));
138 struct utsname uname_info {};
139 uname( &uname_info );
141 .arg(uname_info.sysname, uname_info.release);
145 LOG(VB_HTTP, LOG_INFO, QString(
"HttpServer() - SharePath = %1")
175 m_sslConfig = QSslConfiguration::defaultConfiguration();
178 m_sslConfig.setSslOption(QSsl::SslOptionDisableLegacyRenegotiation,
true);
179 m_sslConfig.setSslOption(QSsl::SslOptionDisableCompression,
true);
181 QList<QSslCipher> availableCiphers = QSslConfiguration::supportedCiphers();
182 QList<QSslCipher> secureCiphers;
183 QList<QSslCipher>::iterator it;
184 for (it = availableCiphers.begin(); it != availableCiphers.end(); ++it)
187 if ((*it).usedBits() < 128)
190 if ((*it).name().startsWith(
"RC4") ||
191 (*it).name().startsWith(
"EXP") ||
192 (*it).name().startsWith(
"ADH") ||
193 (*it).name().contains(
"NULL"))
196 secureCiphers.append(*it);
203 if (hostKeyPath.isEmpty())
206 QFile hostKeyFile(hostKeyPath);
207 if (!hostKeyFile.exists() || !hostKeyFile.open(QIODevice::ReadOnly))
209 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: SSL Host key file (%1) does not exist or is not readable").arg(hostKeyPath));
214 QByteArray rawHostKey = hostKeyFile.readAll();
215 QSslKey hostKey = QSslKey(rawHostKey, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
216 if (!hostKey.isNull())
220 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load host key from file (%1)").arg(hostKeyPath));
225 QSslCertificate hostCert;
226 QList<QSslCertificate> certList = QSslCertificate::fromPath(hostCertPath);
227 if (!certList.isEmpty())
228 hostCert = certList.first();
230 if (!hostCert.isNull())
232 if (hostCert.effectiveDate() > QDateTime::currentDateTime())
234 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Host certificate start date in future (%1)").arg(hostCertPath));
238 if (hostCert.expiryDate() < QDateTime::currentDateTime())
240 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Host certificate has expired (%1)").arg(hostCertPath));
248 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load host cert from file (%1)").arg(hostCertPath));
253 QList< QSslCertificate > CACertList = QSslCertificate::fromPath(caCertPath);
255 if (!CACertList.isEmpty())
257 else if (!caCertPath.isEmpty())
258 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer: Unable to load CA cert file (%1)").arg(caCertPath));
279 if (mythVersion.startsWith(
"v"))
280 mythVersion = mythVersion.right(mythVersion.length() - 1);
292 auto *server = qobject_cast<PrivTcpServer *>(QObject::sender());
294 type = server->GetServerType();
302 QString(
"HttpServer%1").arg(socket));
311 if (pExtension !=
nullptr )
313 LOG(VB_HTTP, LOG_INFO, QString(
"HttpServer: Registering %1 extension").arg(pExtension->
m_sName));
321 for(
const QString& base : std::as_const(list))
324 LOG(VB_HTTP, LOG_INFO, QString(
"HttpServer: Registering %1 extension path %2")
325 .arg(pExtension->
m_sName, base));
337 if (pExtension !=
nullptr )
343 for(
const QString& base : std::as_const(list))
360 bool bProcessed =
false;
362 LOG(VB_HTTP, LOG_DEBUG, QString(
"m_sBaseUrl: %1").arg( pRequest->
m_sBaseUrl ));
367 for (
int nIdx=0; nIdx < list.size() && !bProcessed; nIdx++ )
372 bProcessed = list[ nIdx ]->ProcessOptions(pRequest);
374 bProcessed = list[ nIdx ]->ProcessRequest(pRequest);
378 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer::DelegateRequest - "
379 "Unexpected Exception - "
380 "pExtension->ProcessRequest()."));
391 bProcessed = ext->ProcessOptions(pRequest);
393 bProcessed = ext->ProcessRequest(pRequest);
397 LOG(VB_GENERAL, LOG_ERR, QString(
"HttpServer::DelegateRequest - "
398 "Unexpected Exception - "
399 "pExtension->ProcessRequest()."));
422 timeout = list.first()->GetSocketTimeout();
442 ,
const QSslConfiguration& sslConfig
445 : m_httpServer(httpServer), m_socket(sock),
446 m_socketTimeout(5s), m_connectionType(
type)
448 , m_sslConfig(sslConfig)
451 LOG(VB_HTTP, LOG_INFO, QString(
"HttpWorker(%1): New connection")
461 LOG(VB_HTTP, LOG_DEBUG,
462 QString(
"HttpWorker::run() socket=%1 -- begin").arg(
m_socket));
464 bool bKeepAlive =
true;
466 QTcpSocket *pSocket =
nullptr;
467 bool bEncrypted =
false;
469 static constexpr std::chrono::milliseconds k_poll_interval {1ms};
475 auto *pSslSocket =
new QSslSocket();
476 if (pSslSocket->setSocketDescriptor(
m_socket)
480 pSslSocket->startServerEncryption();
481 attempt_time.
start();
484 pSslSocket->waitForEncrypted(k_poll_interval.count());
486 if (pSslSocket->isEncrypted())
488 LOG(VB_HTTP, LOG_INFO,
"SSL Handshake occurred, connection encrypted");
489 LOG(VB_HTTP, LOG_INFO, QString(
"Using %1 cipher").arg(pSslSocket->sessionCipher().name()));
494 LOG(VB_HTTP, LOG_WARNING,
"SSL Handshake FAILED, connection terminated");
496 pSslSocket =
nullptr;
502 pSslSocket =
nullptr;
506 pSocket = pSslSocket;
515 pSocket =
new QTcpSocket();
516 pSocket->setSocketDescriptor(
m_socket);
526 pSocket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
527 int nRequestsHandled = 0;
532 pSocket->state() == QAbstractSocket::ConnectedState)
538 attempt_time.
start();
540 && pSocket->state() == QAbstractSocket::ConnectedState
541 && pSocket->bytesAvailable() <= 0
545 pSocket->waitForReadyRead(k_poll_interval.count());
557 if (pRequest !=
nullptr)
583 LOG(VB_HTTP, LOG_ERR,
"ParseRequest Failed.");
596 LOG(VB_HTTP, LOG_ERR,
597 QString(
"socket(%1) - Error returned from "
598 "SendResponse... Closing connection")
599 .arg(pSocket->socketDescriptor()));
613 LOG(VB_GENERAL, LOG_ERR,
614 "Error Creating BufferedSocketDeviceRequest");
622 LOG(VB_GENERAL, LOG_ERR,
623 "HttpWorkerThread::ProcessWork - Unexpected Exception.");
628 if ((pSocket->error() != QAbstractSocket::UnknownSocketError) &&
629 (!bKeepAlive || pSocket->error() != QAbstractSocket::SocketTimeoutError))
631 LOG(VB_HTTP, LOG_WARNING, QString(
"HttpWorker(%1): Error %2 (%3)")
633 .arg(pSocket->errorString())
634 .arg(pSocket->error()));
637 std::chrono::milliseconds writeTimeout = 5s;
639 if (pSocket->bytesToWrite() > 0)
641 LOG(VB_HTTP, LOG_DEBUG,
642 QString(
"HttpWorker(%1): Waiting for %2 bytes to be written before closing the connection.")
643 .arg(
m_socket).arg(pSocket->bytesToWrite())
646 attempt_time.
start();
648 pSocket->isValid() &&
649 pSocket->state() == QAbstractSocket::ConnectedState &&
650 pSocket->bytesToWrite() > 0 &&
651 attempt_time.
elapsed() < writeTimeout
662 pSocket->waitForBytesWritten(k_poll_interval.count());
665 if (pSocket->bytesToWrite() > 0)
667 LOG(VB_HTTP, LOG_WARNING, QString(
"HttpWorker(%1): "
668 "Failed to write %2 bytes to "
671 .arg(pSocket->bytesToWrite())
672 .arg(pSocket->errorString()));
675 LOG(VB_HTTP, LOG_INFO, QString(
"HttpWorker(%1): Connection %2 closed. %3 requests were handled")
677 .arg(pSocket->socketDescriptor())
678 .arg(nRequestsHandled));
684 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.