WebServer.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #include "webserver/WebServer.h"
  2. #include "HyperionConfig.h"
  3. #include "StaticFileServing.h"
  4. #include "QtHttpServer.h"
  5. #include <QFileInfo>
  6. #include <QJsonObject>
  7. // netUtil
  8. #include <utils/NetUtils.h>
  9. // Constants
  10. namespace {
  11. const char HTTP_SERVICE_TYPE[] = "http";
  12. const char HTTPS_SERVICE_TYPE[] = "https";
  13. const char HYPERION_SERVICENAME[] = "Hyperion";
  14. } //End of constants
  15. WebServer::WebServer(const QJsonDocument& config, bool useSsl, QObject* parent)
  16. : QObject(parent)
  17. , _config(config)
  18. , _useSsl(useSsl)
  19. , _log(Logger::getInstance("WEBSERVER"))
  20. , _server()
  21. {
  22. }
  23. WebServer::~WebServer()
  24. {
  25. stop();
  26. }
  27. void WebServer::initServer()
  28. {
  29. Debug(_log, "Initialize %s-Webserver", _useSsl ? "https" : "http");
  30. _server = new QtHttpServer(this);
  31. _server->setServerName(QStringLiteral("Hyperion %1-Webserver").arg(_useSsl ? "https" : "http"));
  32. if (_useSsl)
  33. {
  34. _server->setUseSecure();
  35. WEBSERVER_DEFAULT_PORT = 8092;
  36. }
  37. connect(_server, &QtHttpServer::started, this, &WebServer::onServerStarted);
  38. connect(_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped);
  39. connect(_server, &QtHttpServer::error, this, &WebServer::onServerError);
  40. // create StaticFileServing
  41. _staticFileServing = new StaticFileServing(this);
  42. connect(_server, &QtHttpServer::requestNeedsReply, _staticFileServing, &StaticFileServing::onRequestNeedsReply);
  43. // init
  44. handleSettingsUpdate(settings::WEBSERVER, _config);
  45. }
  46. void WebServer::onServerStarted(quint16 port)
  47. {
  48. _inited = true;
  49. Info(_log, "'%s' started on port %d", _server->getServerName().toStdString().c_str(), port);
  50. if (_useSsl)
  51. {
  52. emit publishService(HTTPS_SERVICE_TYPE, _port, HYPERION_SERVICENAME);
  53. }
  54. else
  55. {
  56. emit publishService(HTTP_SERVICE_TYPE, _port, HYPERION_SERVICENAME);
  57. }
  58. emit stateChange(true);
  59. }
  60. void WebServer::onServerStopped()
  61. {
  62. Info(_log, "Stopped %s", _server->getServerName().toStdString().c_str());
  63. emit stateChange(false);
  64. }
  65. void WebServer::onServerError(QString msg)
  66. {
  67. Error(_log, "%s", msg.toStdString().c_str());
  68. }
  69. void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
  70. {
  71. if (type == settings::WEBSERVER)
  72. {
  73. Debug(_log, "Apply Webserver settings");
  74. const QJsonObject& obj = config.object();
  75. _baseUrl = obj["document_root"].toString(WEBSERVER_DEFAULT_PATH);
  76. if ((_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty())
  77. {
  78. QFileInfo info(_baseUrl);
  79. if (!info.exists() || !info.isDir())
  80. {
  81. Error(_log, "document_root '%s' is invalid", _baseUrl.toUtf8().constData());
  82. _baseUrl = WEBSERVER_DEFAULT_PATH;
  83. }
  84. }
  85. else
  86. _baseUrl = WEBSERVER_DEFAULT_PATH;
  87. Debug(_log, "Set document root to: %s", _baseUrl.toUtf8().constData());
  88. _staticFileServing->setBaseUrl(_baseUrl);
  89. // ssl different port
  90. quint16 newPort = _useSsl ? obj["sslPort"].toInt(WEBSERVER_DEFAULT_PORT) : obj["port"].toInt(WEBSERVER_DEFAULT_PORT);
  91. if (_port != newPort)
  92. {
  93. _port = newPort;
  94. stop();
  95. }
  96. // eval if the port is available, will be incremented if not
  97. if (!_server->isListening())
  98. NetUtils::portAvailable(_port, _log);
  99. // on ssl we want .key .cert and probably key password
  100. if (_useSsl)
  101. {
  102. QString keyPath = obj["keyPath"].toString(WEBSERVER_DEFAULT_KEY_PATH);
  103. QString crtPath = obj["crtPath"].toString(WEBSERVER_DEFAULT_CRT_PATH);
  104. QSslKey currKey = _server->getPrivateKey();
  105. QList<QSslCertificate> currCerts = _server->getCertificates();
  106. // check keyPath
  107. if ((keyPath != WEBSERVER_DEFAULT_KEY_PATH) && !keyPath.trimmed().isEmpty())
  108. {
  109. QFileInfo kinfo(keyPath);
  110. if (!kinfo.exists())
  111. {
  112. Error(_log, "No SSL key found at '%s' falling back to internal", keyPath.toUtf8().constData());
  113. keyPath = WEBSERVER_DEFAULT_KEY_PATH;
  114. }
  115. }
  116. else
  117. keyPath = WEBSERVER_DEFAULT_KEY_PATH;
  118. // check crtPath
  119. if ((crtPath != WEBSERVER_DEFAULT_CRT_PATH) && !crtPath.trimmed().isEmpty())
  120. {
  121. QFileInfo cinfo(crtPath);
  122. if (!cinfo.exists())
  123. {
  124. Error(_log, "No SSL certificate found at '%s' falling back to internal", crtPath.toUtf8().constData());
  125. crtPath = WEBSERVER_DEFAULT_CRT_PATH;
  126. }
  127. }
  128. else
  129. crtPath = WEBSERVER_DEFAULT_CRT_PATH;
  130. // load and verify crt
  131. QFile cfile(crtPath);
  132. cfile.open(QIODevice::ReadOnly);
  133. QList<QSslCertificate> validList;
  134. QList<QSslCertificate> cList = QSslCertificate::fromDevice(&cfile, QSsl::Pem);
  135. cfile.close();
  136. // Filter for valid certs
  137. for (const auto& entry : cList) {
  138. if (!entry.isNull() && QDateTime::currentDateTime().daysTo(entry.expiryDate()) > 0)
  139. validList.append(entry);
  140. else
  141. Error(_log, "The provided SSL certificate is invalid/not supported/reached expiry date ('%s')", crtPath.toUtf8().constData());
  142. }
  143. if (!validList.isEmpty()) {
  144. Debug(_log, "Setup SSL certificate");
  145. _server->setCertificates(validList);
  146. }
  147. else {
  148. Error(_log, "No valid SSL certificate has been found ('%s')", crtPath.toUtf8().constData());
  149. }
  150. // load and verify key
  151. QFile kfile(keyPath);
  152. kfile.open(QIODevice::ReadOnly);
  153. // The key should be RSA enrcrypted and PEM format, optional the passPhrase
  154. QSslKey key(&kfile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, obj["keyPassPhrase"].toString().toUtf8());
  155. kfile.close();
  156. if (key.isNull()) {
  157. Error(_log, "The provided SSL key is invalid or not supported use RSA encrypt and PEM format ('%s')", keyPath.toUtf8().constData());
  158. }
  159. else {
  160. Debug(_log, "Setup private SSL key");
  161. _server->setPrivateKey(key);
  162. }
  163. }
  164. start();
  165. emit portChanged(_port);
  166. }
  167. }
  168. void WebServer::start()
  169. {
  170. _server->start(_port);
  171. }
  172. void WebServer::stop()
  173. {
  174. _server->stop();
  175. }
  176. void WebServer::setSSDPDescription(const QString& desc)
  177. {
  178. _staticFileServing->setSSDPDescription(desc);
  179. }