From 957d697aee8bb9a94c35a1474704ea483951870f Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sun, 10 May 2020 02:41:00 +0800 Subject: [PATCH 1/4] Prebuild HTTP headers as much as possible This avoids some branching when building a HTTP response. --- src/webui/webapplication.cpp | 43 ++++++++++++++++-------------------- src/webui/webapplication.h | 6 ++--- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index ec5073d89..76fbb54b6 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -335,25 +335,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 +374,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}); } } } @@ -460,22 +469,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 CustomHTTPHeader &prebuiltHeader : asConst(m_prebuiltHeaders)) + header(prebuiltHeader.name, prebuiltHeader.value); return response(); } diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index a0d145bb9..d5f7ffcaa 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -151,12 +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 @@ -164,6 +162,6 @@ private: QString name; QString value; }; - bool m_useCustomHTTPHeaders; - QVector m_customHTTPHeaders; + + QVector m_prebuiltHeaders; }; From 74a2168bc145f7e94559cb49627dda2b5e6523f2 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sun, 10 May 2020 02:48:21 +0800 Subject: [PATCH 2/4] Define and use a proper HTTP header structure --- src/base/http/responsebuilder.cpp | 4 ++-- src/base/http/responsebuilder.h | 2 +- src/base/http/types.h | 6 ++++++ src/webui/webapplication.cpp | 12 ++++++------ src/webui/webapplication.h | 9 +-------- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/base/http/responsebuilder.cpp b/src/base/http/responsebuilder.cpp index fd136a224..6371cf6fc 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::header(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..6de4a0878 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 header(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..e6d6d153c 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -92,6 +92,12 @@ namespace Http QByteArray data; }; + struct Header + { + QString name; + QString value; + }; + struct Request { QString version; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 76fbb54b6..2861e2cbb 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -400,7 +400,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)); + header({Http::HEADER_CACHE_CONTROL, getCachingInterval(it->mimeType)}); return; } @@ -432,7 +432,7 @@ void WebApplication::sendFile(const QString &path) } print(data, mimeType.name()); - header(Http::HEADER_CACHE_CONTROL, getCachingInterval(mimeType.name())); + header({Http::HEADER_CACHE_CONTROL, getCachingInterval(mimeType.name())}); } Http::Response WebApplication::processRequest(const Http::Request &request, const Http::Environment &env) @@ -469,8 +469,8 @@ Http::Response WebApplication::processRequest(const Http::Request &request, cons print(error.message(), Http::CONTENT_TYPE_TXT); } - for (const CustomHTTPHeader &prebuiltHeader : asConst(m_prebuiltHeaders)) - header(prebuiltHeader.name, prebuiltHeader.value); + for (const Http::Header &prebuiltHeader : asConst(m_prebuiltHeaders)) + header(prebuiltHeader); return response(); } @@ -562,7 +562,7 @@ void WebApplication::sessionStart() QByteArray cookieRawForm = cookie.toRawForm(); if (m_isCSRFProtectionEnabled) cookieRawForm.append("; SameSite=Strict"); - header(Http::HEADER_SET_COOKIE, cookieRawForm); + header({Http::HEADER_SET_COOKIE, cookieRawForm}); } void WebApplication::sessionEnd() @@ -576,7 +576,7 @@ void WebApplication::sessionEnd() delete m_sessions.take(m_currentSession->id()); m_currentSession = nullptr; - header(Http::HEADER_SET_COOKIE, cookie.toRawForm()); + header({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 d5f7ffcaa..0ca070f21 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -156,12 +156,5 @@ private: bool m_isHostHeaderValidationEnabled; bool m_isHttpsEnabled; - // Custom HTTP headers - struct CustomHTTPHeader - { - QString name; - QString value; - }; - - QVector m_prebuiltHeaders; + QVector m_prebuiltHeaders; }; From d57b9be706e0916bbca13dec7ee36c180037fe0d Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sun, 10 May 2020 02:51:37 +0800 Subject: [PATCH 3/4] Rename function --- src/base/http/responsebuilder.cpp | 2 +- src/base/http/responsebuilder.h | 2 +- src/webui/webapplication.cpp | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/base/http/responsebuilder.cpp b/src/base/http/responsebuilder.cpp index 6371cf6fc..74a63482b 100644 --- a/src/base/http/responsebuilder.cpp +++ b/src/base/http/responsebuilder.cpp @@ -35,7 +35,7 @@ void ResponseBuilder::status(const uint code, const QString &text) m_response.status = {code, text}; } -void ResponseBuilder::header(const Header &header) +void ResponseBuilder::setHeader(const Header &header) { m_response.headers[header.name] = header.value; } diff --git a/src/base/http/responsebuilder.h b/src/base/http/responsebuilder.h index 6de4a0878..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 Header &header); + 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/webui/webapplication.cpp b/src/webui/webapplication.cpp index 2861e2cbb..4eaa495f9 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -400,7 +400,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; } @@ -432,7 +432,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) @@ -470,7 +470,7 @@ Http::Response WebApplication::processRequest(const Http::Request &request, cons } for (const Http::Header &prebuiltHeader : asConst(m_prebuiltHeaders)) - header(prebuiltHeader); + setHeader(prebuiltHeader); return response(); } @@ -562,7 +562,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() @@ -576,7 +576,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 From be152f45c9db11f55c8595145bdca3abd7e1b77f Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sun, 10 May 2020 03:00:56 +0800 Subject: [PATCH 4/4] Define and use Http::HeaderMap type --- src/base/http/requestparser.cpp | 4 ++-- src/base/http/types.h | 8 ++++---- src/webui/webapplication.cpp | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) 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/types.h b/src/base/http/types.h index e6d6d153c..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"; @@ -98,12 +96,14 @@ namespace Http QString value; }; + using HeaderMap = QMap; //
+ struct Request { QString version; QString method; QString path; - QStringMap headers; + HeaderMap headers; QHash query; QHash posts; QVector files; @@ -118,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 4eaa495f9..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"