mirror of
https://github.com/znc/znc.git
synced 2026-08-07 01:13:25 +02:00
HTTPSock: emit standard hardening response headers
Close #2012. Add X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff and Referrer-Policy: same-origin to every response so webadmin and module pages are framed/sniff-protected by default. Add no-store Cache-Control and Pragma: no-cache on dynamic responses so shared workstations can't replay authenticated pages from browser history. Skip the cache headers for 304 and for static asset MIME types (image, font, text/css, application/javascript) that the existing ETag/Last-Modified path on PrintFile already handles. Per review feedback: the emitter is a private WriteHardeningHeaders that writes each line via the socket directly from PrintHeader, not a public helper returning a temporary VCString. Callers can override a default value with AddHeader, or suppress one outright with the new public OmitHardeningHeader(name). Tests: drive PrintHeader on a CHTTPSock subclass that captures Write() calls, then assert with gmock matchers (Contains(StartsWith(...))).
This commit is contained in:
@@ -740,6 +740,8 @@ bool CHTTPSock::PrintHeader(off_t uContentLength, const CString& sContentType,
|
||||
}
|
||||
Write("Content-Type: " + m_sContentType + "\r\n");
|
||||
|
||||
WriteHardeningHeaders(uStatusId);
|
||||
|
||||
for (const auto& it : m_msResponseCookies) {
|
||||
Write("Set-Cookie: " + it.first.Escape_n(CString::EURL) + "=" +
|
||||
it.second.Escape_n(CString::EURL) + "; HttpOnly; path=/;" +
|
||||
@@ -776,6 +778,38 @@ void CHTTPSock::AddHeader(const CString& sName, const CString& sValue) {
|
||||
m_msHeaders[sName] = sValue;
|
||||
}
|
||||
|
||||
void CHTTPSock::OmitHardeningHeader(const CString& sName) {
|
||||
m_ssOmitHardening.insert(sName);
|
||||
}
|
||||
|
||||
void CHTTPSock::WriteHardeningHeaders(unsigned int uStatusId) {
|
||||
auto writeIfWanted = [&](const CString& sName, const CString& sValue) {
|
||||
if (m_msHeaders.find(sName) != m_msHeaders.end()) return;
|
||||
if (m_ssOmitHardening.find(sName) != m_ssOmitHardening.end()) return;
|
||||
Write(sName + ": " + sValue + "\r\n");
|
||||
};
|
||||
|
||||
// Always-on defaults: callers can override via AddHeader, or skip
|
||||
// entirely via OmitHardeningHeader, before PrintHeader runs.
|
||||
writeIfWanted("X-Frame-Options", "SAMEORIGIN");
|
||||
writeIfWanted("X-Content-Type-Options", "nosniff");
|
||||
writeIfWanted("Referrer-Policy", "same-origin");
|
||||
|
||||
// Don't cache authenticated/dynamic responses. Skip for 304 and for
|
||||
// static asset MIME types that the ETag/Last-Modified path handles
|
||||
// explicitly via PrintFile.
|
||||
const bool bStaticLike =
|
||||
uStatusId == 304 || m_sContentType.StartsWith("image/") ||
|
||||
m_sContentType.StartsWith("font/") ||
|
||||
m_sContentType.StartsWith("text/css") ||
|
||||
m_sContentType.StartsWith("application/javascript");
|
||||
if (!bStaticLike) {
|
||||
writeIfWanted("Cache-Control",
|
||||
"no-store, no-cache, must-revalidate, max-age=0");
|
||||
writeIfWanted("Pragma", "no-cache");
|
||||
}
|
||||
}
|
||||
|
||||
bool CHTTPSock::Redirect(const CString& sURL) {
|
||||
if (SentHeader()) {
|
||||
DEBUG("Redirect() - Header was already sent");
|
||||
|
||||
Reference in New Issue
Block a user