Skip to content

Commit 39ce4ef

Browse files
feat: helmet middleware and tests.
Signed-off-by: Amlal El Mahrouss <[email protected]>
1 parent 08339fe commit 39ce4ef

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// Copyright (c) 2025 Amlal El Mahrouss (amlal at nekernel dot org)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/http_proto
8+
//
9+
10+
#ifndef BOOST_HTTP_PROTO_SERVER_HELMET_HPP
11+
#define BOOST_HTTP_PROTO_SERVER_HELMET_HPP
12+
13+
#include <boost/http_proto/detail/config.hpp>
14+
#include <boost/http_proto/server/route_handler.hpp>
15+
16+
namespace boost {
17+
namespace http_proto {
18+
19+
/// \brief Helmet middleware options.
20+
struct helmet_options
21+
{
22+
using helmet_pair = std::pair<std::string, std::vector<std::string>>;
23+
using helmet_map = std::vector<helmet_pair>;
24+
25+
/// \brief {key, enabled}
26+
/// \note i.e {bad-header, ""} <-- disabled
27+
struct helmet_headers_option {
28+
helmet_map headers = {
29+
{"Content-Security-Policy", {"default-src 'self'", "base-uri 'self'", "font-src 'self' https: data:", "form-action 'self'", "frame-ancestors 'self'",
30+
"img-src 'self' data:", "object-src 'none'", "script-src 'self'", "script-src-attr 'none'", "style-src 'self' https: 'unsafe-inline'", "upgrade-insecure-requests"}},
31+
{"Cross-Origin-Embedder-Policy", {"require-corp"}},
32+
{"Cross-Origin-Opener-Policy", {"same-origin"}},
33+
{"Cross-Origin-Resource-Policy", {"same-origin"}},
34+
{"X-DNS-Prefetch-Control", {"off"}},
35+
{"Expect-CT", {"max-age=86400, enforce"}},
36+
{"X-Frame-Options", {"SAMEORIGIN"}},
37+
{"X-Powered-By", {""}}, // Remove this header
38+
{"Strict-Transport-Security", {"max-age=15552000", "includeSubDomains"}},
39+
{"X-Download-Options", {"noopen"}},
40+
{"X-Content-Type-Options", {"nosniff"}},
41+
{"Origin-Agent-Cluster", {"?1"}},
42+
{"X-Permitted-Cross-Domain-Policies", {"none"}},
43+
{"Referrer-Policy", {"no-referrer"}},
44+
{"X-XSS-Protection", {"0"}} // Disabled as modern browsers have better protections
45+
};
46+
} requestHeaders;
47+
};
48+
49+
/// \brief Middleware inspired by express.js concept of helmets.
50+
class helmet
51+
{
52+
struct impl;
53+
std::unique_ptr<impl> impl_;
54+
55+
public:
56+
/// \brief Builds an helmet and compute its options for caching purposes.
57+
BOOST_HTTP_PROTO_DECL
58+
explicit helmet(
59+
helmet_options options = {}) noexcept;
60+
61+
/// \brief Iterates over cachedHeaders and apply its rules to the response params.
62+
/// \param p route parameter argument
63+
/// \return route_result an error_code signaling the route's status.
64+
BOOST_HTTP_PROTO_DECL
65+
route_result
66+
operator()(route_params& p) const;
67+
68+
private:
69+
helmet_options options_;
70+
};
71+
}
72+
73+
}
74+
#endif

src/server/helmet.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// Copyright (c) 2025 Amlal El Mahrouss (amlal at nekernel dot org)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/http_proto
8+
//
9+
10+
#include <boost/http_proto/server/helmet.hpp>
11+
#include <algorithm>
12+
13+
namespace boost {
14+
namespace http_proto {
15+
16+
namespace detail {
17+
/// \internal
18+
auto setHeaderValues(const std::vector<std::string>& fields_value) -> std::string {
19+
if (fields_value.empty())
20+
detail::throw_invalid_argument();
21+
22+
std::string res_value;
23+
bool first = false;
24+
25+
for (const auto& value: fields_value)
26+
{
27+
if (first) res_value += "; ";
28+
res_value += value;
29+
first = true;
30+
}
31+
32+
return res_value;
33+
}
34+
}
35+
36+
struct helmet::impl
37+
{
38+
using cached_pair = std::pair<std::string, std::string>;
39+
using cached_vector = std::vector<cached_pair>;
40+
41+
impl() = default;
42+
~impl() = default;
43+
44+
cached_vector cachedHeaders;
45+
};
46+
47+
helmet::
48+
helmet(
49+
helmet_options options) noexcept
50+
: options_(std::move(options))
51+
{
52+
std::for_each(options_.requestHeaders.headers.begin(), options_.requestHeaders.headers.end(),
53+
[this] (const auto& hdr)
54+
{
55+
if (hdr.second.empty())
56+
detail::throw_invalid_argument();
57+
58+
this->impl_->cachedHeaders.push_back(std::make_pair(hdr.first, detail::setHeaderValues(hdr.second)));
59+
});
60+
}
61+
62+
route_result
63+
helmet::
64+
operator()(
65+
route_params& p) const
66+
{
67+
std::for_each(impl_->cachedHeaders.begin(), impl_->cachedHeaders.end(),
68+
[&p] (const auto& hdr)
69+
{
70+
p.res.set(hdr.first, hdr.second);
71+
});
72+
73+
return route::next;
74+
}
75+
76+
}
77+
}

test/unit/server/helmet.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright (c) 2025 Amlal El Mahrouss (amlal at nekernel dot org)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/http_proto
8+
//
9+
10+
#include <boost/http_proto/server/helmet.hpp>
11+
#include "test_suite.hpp"
12+
13+
namespace boost {
14+
namespace http_proto {
15+
16+
struct helmet_test
17+
{
18+
void run() {}
19+
};
20+
21+
TEST_SUITE(
22+
helmet_test,
23+
"boost.http_proto.server.helmet");
24+
}
25+
26+
}

0 commit comments

Comments
 (0)