diff --git a/src/base/http/requestparser.cpp b/src/base/http/requestparser.cpp index ef7a94c30..e6e1853f6 100644 --- a/src/base/http/requestparser.cpp +++ b/src/base/http/requestparser.cpp @@ -57,7 +57,7 @@ namespace return in; } - bool parseHeaderLine(const QString &line, QStringMap &out) + bool parseHeaderLine(const QString &line, HeaderMap &out) { // [rfc7230] 3.2. Header Fields const int i = line.indexOf(':'); @@ -287,7 +287,7 @@ bool RequestParser::parseFormData(const QByteArray &data) const QString headers = QString::fromLatin1(list[0]); const QByteArray payload = viewWithoutEndingWith(list[1], CRLF); - QStringMap headersMap; + HeaderMap headersMap; const QVector headerLines = headers.splitRef(CRLF, QString::SkipEmptyParts); for (const auto &line : headerLines) { if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive)) { diff --git a/src/base/http/responsebuilder.cpp b/src/base/http/responsebuilder.cpp index fd136a224..74a63482b 100644 --- a/src/base/http/responsebuilder.cpp +++ b/src/base/http/responsebuilder.cpp @@ -35,9 +35,9 @@ void ResponseBuilder::status(const uint code, const QString &text) m_response.status = {code, text}; } -void ResponseBuilder::header(const QString &name, const QString &value) +void ResponseBuilder::setHeader(const Header &header) { - m_response.headers[name] = value; + m_response.headers[header.name] = header.value; } void ResponseBuilder::print(const QString &text, const QString &type) diff --git a/src/base/http/responsebuilder.h b/src/base/http/responsebuilder.h index f1053cc81..e2727c74d 100644 --- a/src/base/http/responsebuilder.h +++ b/src/base/http/responsebuilder.h @@ -37,7 +37,7 @@ namespace Http { public: void status(uint code = 200, const QString &text = QLatin1String("OK")); - void header(const QString &name, const QString &value); + void setHeader(const Header &header); void print(const QString &text, const QString &type = CONTENT_TYPE_HTML); void print(const QByteArray &data, const QString &type = CONTENT_TYPE_HTML); void clear(); diff --git a/src/base/http/types.h b/src/base/http/types.h index d63d1bbb1..0c1cb2f14 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -34,8 +34,6 @@ #include #include -#include "base/types.h" - namespace Http { const char METHOD_GET[] = "GET"; @@ -92,12 +90,20 @@ namespace Http QByteArray data; }; + struct Header + { + QString name; + QString value; + }; + + using HeaderMap = QMap; //
+ struct Request { QString version; QString method; QString path; - QStringMap headers; + HeaderMap headers; QHash query; QHash posts; QVector files; @@ -112,7 +118,7 @@ namespace Http struct Response { ResponseStatus status; - QStringMap headers; + HeaderMap headers; QByteArray content; Response(uint code = 200, const QString &text = "OK") diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index ec5073d89..021c7a1bd 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -46,6 +46,7 @@ #include "base/http/httperror.h" #include "base/logger.h" #include "base/preferences.h" +#include "base/types.h" #include "base/utils/bytearray.h" #include "base/utils/fs.h" #include "base/utils/misc.h" @@ -335,25 +336,34 @@ void WebApplication::configure() m_domainList = pref->getServerDomains().split(';', QString::SkipEmptyParts); std::for_each(m_domainList.begin(), m_domainList.end(), [](QString &entry) { entry = entry.trimmed(); }); - m_isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled(); m_isCSRFProtectionEnabled = pref->isWebUiCSRFProtectionEnabled(); m_isSecureCookieEnabled = pref->isWebUiSecureCookieEnabled(); m_isHostHeaderValidationEnabled = pref->isWebUIHostHeaderValidationEnabled(); m_isHttpsEnabled = pref->isWebUiHttpsEnabled(); - m_contentSecurityPolicy = + m_prebuiltHeaders.clear(); + m_prebuiltHeaders.push_back({QLatin1String(Http::HEADER_X_XSS_PROTECTION), QLatin1String("1; mode=block")}); + m_prebuiltHeaders.push_back({QLatin1String(Http::HEADER_X_CONTENT_TYPE_OPTIONS), QLatin1String("nosniff")}); + + if (!m_isAltUIUsed) + m_prebuiltHeaders.push_back({QLatin1String(Http::HEADER_REFERRER_POLICY), QLatin1String("same-origin")}); + + const bool isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled(); + if (isClickjackingProtectionEnabled) + m_prebuiltHeaders.push_back({QLatin1String(Http::HEADER_X_FRAME_OPTIONS), QLatin1String("SAMEORIGIN")}); + + const QString contentSecurityPolicy = (m_isAltUIUsed ? QLatin1String("") : QLatin1String("default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none'; form-action 'self';")) - + (m_isClickjackingProtectionEnabled ? QLatin1String(" frame-ancestors 'self';") : QLatin1String("")) + + (isClickjackingProtectionEnabled ? QLatin1String(" frame-ancestors 'self';") : QLatin1String("")) + (m_isHttpsEnabled ? QLatin1String(" upgrade-insecure-requests;") : QLatin1String("")); + if (!contentSecurityPolicy.isEmpty()) + m_prebuiltHeaders.push_back({QLatin1String(Http::HEADER_CONTENT_SECURITY_POLICY), contentSecurityPolicy}); - m_useCustomHTTPHeaders = pref->isWebUICustomHTTPHeadersEnabled(); - m_customHTTPHeaders.clear(); - if (m_useCustomHTTPHeaders) { + if (pref->isWebUICustomHTTPHeadersEnabled()) { const QString customHeaders = pref->getWebUICustomHTTPHeaders().trimmed(); const QVector customHeaderLines = customHeaders.splitRef('\n', QString::SkipEmptyParts); - m_customHTTPHeaders.reserve(customHeaderLines.size()); for (const QStringRef &line : customHeaderLines) { const int idx = line.indexOf(':'); @@ -365,7 +375,7 @@ void WebApplication::configure() const QString header = line.left(idx).trimmed().toString(); const QString value = line.mid(idx + 1).trimmed().toString(); - m_customHTTPHeaders.push_back({header, value}); + m_prebuiltHeaders.push_back({header, value}); } } } @@ -391,7 +401,7 @@ void WebApplication::sendFile(const QString &path) const auto it = m_translatedFiles.constFind(path); if ((it != m_translatedFiles.constEnd()) && (lastModified <= it->lastModified)) { print(it->data, it->mimeType); - header(Http::HEADER_CACHE_CONTROL, getCachingInterval(it->mimeType)); + setHeader({Http::HEADER_CACHE_CONTROL, getCachingInterval(it->mimeType)}); return; } @@ -423,7 +433,7 @@ void WebApplication::sendFile(const QString &path) } print(data, mimeType.name()); - header(Http::HEADER_CACHE_CONTROL, getCachingInterval(mimeType.name())); + setHeader({Http::HEADER_CACHE_CONTROL, getCachingInterval(mimeType.name())}); } Http::Response WebApplication::processRequest(const Http::Request &request, const Http::Environment &env) @@ -460,22 +470,8 @@ Http::Response WebApplication::processRequest(const Http::Request &request, cons print(error.message(), Http::CONTENT_TYPE_TXT); } - header(QLatin1String(Http::HEADER_X_XSS_PROTECTION), QLatin1String("1; mode=block")); - header(QLatin1String(Http::HEADER_X_CONTENT_TYPE_OPTIONS), QLatin1String("nosniff")); - - if (m_isClickjackingProtectionEnabled) - header(QLatin1String(Http::HEADER_X_FRAME_OPTIONS), QLatin1String("SAMEORIGIN")); - - if (!m_isAltUIUsed) - header(QLatin1String(Http::HEADER_REFERRER_POLICY), QLatin1String("same-origin")); - - if (!m_contentSecurityPolicy.isEmpty()) - header(QLatin1String(Http::HEADER_CONTENT_SECURITY_POLICY), m_contentSecurityPolicy); - - if (m_useCustomHTTPHeaders) { - for (const CustomHTTPHeader &customHeader : asConst(m_customHTTPHeaders)) - header(customHeader.name, customHeader.value); - } + for (const Http::Header &prebuiltHeader : asConst(m_prebuiltHeaders)) + setHeader(prebuiltHeader); return response(); } @@ -567,7 +563,7 @@ void WebApplication::sessionStart() QByteArray cookieRawForm = cookie.toRawForm(); if (m_isCSRFProtectionEnabled) cookieRawForm.append("; SameSite=Strict"); - header(Http::HEADER_SET_COOKIE, cookieRawForm); + setHeader({Http::HEADER_SET_COOKIE, cookieRawForm}); } void WebApplication::sessionEnd() @@ -581,7 +577,7 @@ void WebApplication::sessionEnd() delete m_sessions.take(m_currentSession->id()); m_currentSession = nullptr; - header(Http::HEADER_SET_COOKIE, cookie.toRawForm()); + setHeader({Http::HEADER_SET_COOKIE, cookie.toRawForm()}); } bool WebApplication::isCrossSiteRequest(const Http::Request &request) const diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index a0d145bb9..0ca070f21 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -151,19 +151,10 @@ private: // security related QStringList m_domainList; - bool m_isClickjackingProtectionEnabled; bool m_isCSRFProtectionEnabled; bool m_isSecureCookieEnabled; bool m_isHostHeaderValidationEnabled; bool m_isHttpsEnabled; - QString m_contentSecurityPolicy; - // Custom HTTP headers - struct CustomHTTPHeader - { - QString name; - QString value; - }; - bool m_useCustomHTTPHeaders; - QVector m_customHTTPHeaders; + QVector m_prebuiltHeaders; };