Consolidate header field-pair validation into fields::is_field_valid

The is_field_name(name) && is_field_value(value) predicate was repeated
across five output paths (set_header, write_headers, write_content_chunked
trailer, perform_websocket_handshake, check_and_write_headers). Introduce
fields::is_field_valid(name, value) and route all five through it so the
CR/LF-injection guard has a single definition. No behavior change.
This commit is contained in:
yhirose
2026-07-23 17:34:57 -04:00
parent 613a41b49d
commit cda2bb5342
+10 -15
View File
@@ -3426,6 +3426,7 @@ bool is_obs_text(char c);
bool is_field_vchar(char c); bool is_field_vchar(char c);
bool is_field_content(const std::string &s); bool is_field_content(const std::string &s);
bool is_field_value(const std::string &s); bool is_field_value(const std::string &s);
bool is_field_valid(const std::string &name, const std::string &value);
} // namespace fields } // namespace fields
} // namespace detail } // namespace detail
@@ -7203,9 +7204,7 @@ get_multimap_value(const Map &m, const std::string &key, size_t id) {
inline void set_header(Headers &headers, const std::string &key, inline void set_header(Headers &headers, const std::string &key,
const std::string &val) { const std::string &val) {
if (fields::is_field_name(key) && fields::is_field_value(val)) { if (fields::is_field_valid(key, val)) { headers.emplace(key, val); }
headers.emplace(key, val);
}
} }
inline bool read_headers(Stream &strm, Headers &headers) { inline bool read_headers(Stream &strm, Headers &headers) {
@@ -7599,9 +7598,7 @@ inline ssize_t write_headers(Stream &strm, const Headers &headers) {
// headers up front in check_and_write_headers, but the server passes // headers up front in check_and_write_headers, but the server passes
// res.headers straight to this writer, and res.headers is a public field // res.headers straight to this writer, and res.headers is a public field
// an application can populate directly with request-derived values. // an application can populate directly with request-derived values.
if (!fields::is_field_name(x.first) || !fields::is_field_value(x.second)) { if (!fields::is_field_valid(x.first, x.second)) { continue; }
continue;
}
std::string s; std::string s;
s = x.first; s = x.first;
@@ -7803,10 +7800,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
for (const auto &kv : *trailer) { for (const auto &kv : *trailer) {
// Skip fields with invalid names or values to prevent response // Skip fields with invalid names or values to prevent response
// splitting via CR/LF injection, matching set_header(). // splitting via CR/LF injection, matching set_header().
if (!fields::is_field_name(kv.first) || if (!fields::is_field_valid(kv.first, kv.second)) { continue; }
!fields::is_field_value(kv.second)) {
continue;
}
std::string field_line = kv.first + ": " + kv.second + "\r\n"; std::string field_line = kv.first + ": " + kv.second + "\r\n";
if (!write_data(strm, field_line.data(), field_line.size())) { if (!write_data(strm, field_line.data(), field_line.size())) {
ok = false; ok = false;
@@ -9012,6 +9006,10 @@ inline bool is_field_content(const std::string &s) {
inline bool is_field_value(const std::string &s) { return is_field_content(s); } inline bool is_field_value(const std::string &s) { return is_field_content(s); }
inline bool is_field_valid(const std::string &name, const std::string &value) {
return is_field_name(name) && is_field_value(value);
}
} // namespace fields } // namespace fields
inline bool perform_websocket_handshake(Stream &strm, const std::string &host, inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
@@ -9026,9 +9024,7 @@ inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
// Validate user-provided headers // Validate user-provided headers
for (const auto &h : headers) { for (const auto &h : headers) {
if (!fields::is_field_name(h.first) || !fields::is_field_value(h.second)) { if (!fields::is_field_valid(h.first, h.second)) { return false; }
return false;
}
} }
// Generate random Sec-WebSocket-Key // Generate random Sec-WebSocket-Key
@@ -11080,8 +11076,7 @@ template <typename T>
inline bool check_and_write_headers(Stream &strm, Headers &headers, inline bool check_and_write_headers(Stream &strm, Headers &headers,
T header_writer, Error &error) { T header_writer, Error &error) {
for (const auto &h : headers) { for (const auto &h : headers) {
if (!detail::fields::is_field_name(h.first) || if (!detail::fields::is_field_valid(h.first, h.second)) {
!detail::fields::is_field_value(h.second)) {
error = Error::InvalidHeaders; error = Error::InvalidHeaders;
return false; return false;
} }