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:
MarkLee131
2026-04-29 20:29:47 +08:00
parent 866303eef0
commit f71e021e3b
3 changed files with 162 additions and 0 deletions
+11
View File
@@ -56,6 +56,11 @@ class CHTTPSock : public CSocket {
* inputs that could split the response into a separate header or body
* (RFC 7230 disallows obs-fold; treat any bare CR/LF as invalid). */
static bool IsValidHeaderField(const CString& s);
/** Suppress one of the default security/cache hardening headers for
* the next response. Use this when the caller does not want a value
* sent at all (a different value can instead be supplied via
* AddHeader). Must be called before PrintHeader. */
void OmitHardeningHeader(const CString& sName);
void SetContentType(const CString& sContentType);
bool PrintNotFound();
@@ -121,6 +126,11 @@ class CHTTPSock : public CSocket {
void WriteFileUncompressed(CFile& File);
void WriteFileGzipped(CFile& File);
/** Write the security/cache hardening header lines for the current
* response. Skips any name that the caller has already populated via
* AddHeader, or has explicitly omitted via OmitHardeningHeader. */
void WriteHardeningHeaders(unsigned int uStatusId);
protected:
void PrintPage(const CString& sPage);
void Init();
@@ -142,6 +152,7 @@ class CHTTPSock : public CSocket {
std::map<CString, VCString> m_msvsPOSTParams;
std::map<CString, VCString> m_msvsGETParams;
MCString m_msHeaders;
SCString m_ssOmitHardening;
bool m_bHTTP10Client;
CString m_sIfNoneMatch;
bool m_bAcceptGzip;