1
0
mirror of synced 2025-07-17 17:40:57 +03:00

Merge commit from fork

* Fix HTTP Header Smuggling due to insecure trailers merge

* Improve performance
This commit is contained in:
yhirose
2025-07-09 07:10:09 -04:00
committed by GitHub
parent 802743264c
commit 17ba303889
2 changed files with 156 additions and 5 deletions

View File

@ -450,6 +450,10 @@ struct hash {
}
};
template <typename T>
using unordered_set = std::unordered_set<T, detail::case_ignore::hash,
detail::case_ignore::equal_to>;
} // namespace case_ignore
// This is based on
@ -710,6 +714,7 @@ struct Request {
std::string matched_route;
Params params;
Headers headers;
Headers trailers;
std::string body;
std::string remote_addr;
@ -744,6 +749,10 @@ struct Request {
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
bool has_trailer(const std::string &key) const;
std::string get_trailer_value(const std::string &key, size_t id = 0) const;
size_t get_trailer_value_count(const std::string &key) const;
bool has_param(const std::string &key) const;
std::string get_param_value(const std::string &key, size_t id = 0) const;
size_t get_param_value_count(const std::string &key) const;
@ -765,6 +774,7 @@ struct Response {
int status = -1;
std::string reason;
Headers headers;
Headers trailers;
std::string body;
std::string location; // Redirect location
@ -776,6 +786,10 @@ struct Response {
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
bool has_trailer(const std::string &key) const;
std::string get_trailer_value(const std::string &key, size_t id = 0) const;
size_t get_trailer_value_count(const std::string &key) const;
void set_redirect(const std::string &url, int status = StatusCode::Found_302);
void set_content(const char *s, size_t n, const std::string &content_type);
void set_content(const std::string &s, const std::string &content_type);
@ -4727,6 +4741,42 @@ inline ReadContentResult read_content_chunked(Stream &strm, T &x,
// chunked transfer coding data without the final CRLF.
if (!line_reader.getline()) { return ReadContentResult::Success; }
// RFC 7230 Section 4.1.2 - Headers prohibited in trailers
thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
// Message framing
"transfer-encoding", "content-length",
// Routing
"host",
// Authentication
"authorization", "www-authenticate", "proxy-authenticate",
"proxy-authorization", "cookie", "set-cookie",
// Request modifiers
"cache-control", "expect", "max-forwards", "pragma", "range", "te",
// Response control
"age", "expires", "date", "location", "retry-after", "vary", "warning",
// Payload processing
"content-encoding", "content-type", "content-range", "trailer"};
// Parse declared trailer headers once for performance
case_ignore::unordered_set<std::string> declared_trailers;
if (has_header(x.headers, "Trailer")) {
auto trailer_header = get_header_value(x.headers, "Trailer", "", 0);
auto len = std::strlen(trailer_header);
split(trailer_header, trailer_header + len, ',',
[&](const char *b, const char *e) {
std::string key(b, e);
if (prohibited_trailers.find(key) == prohibited_trailers.end()) {
declared_trailers.insert(key);
}
});
}
size_t trailer_header_count = 0;
while (strcmp(line_reader.ptr(), "\r\n") != 0) {
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) {
@ -4744,11 +4794,12 @@ inline ReadContentResult read_content_chunked(Stream &strm, T &x,
parse_header(line_reader.ptr(), end,
[&](const std::string &key, const std::string &val) {
x.headers.emplace(key, val);
if (declared_trailers.find(key) != declared_trailers.end()) {
x.trailers.emplace(key, val);
trailer_header_count++;
}
});
trailer_header_count++;
if (!line_reader.getline()) { return ReadContentResult::Error; }
}
@ -6468,6 +6519,24 @@ inline void Request::set_header(const std::string &key,
}
}
inline bool Request::has_trailer(const std::string &key) const {
return trailers.find(key) != trailers.end();
}
inline std::string Request::get_trailer_value(const std::string &key,
size_t id) const {
auto rng = trailers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
if (it != rng.second) { return it->second; }
return std::string();
}
inline size_t Request::get_trailer_value_count(const std::string &key) const {
auto r = trailers.equal_range(key);
return static_cast<size_t>(std::distance(r.first, r.second));
}
inline bool Request::has_param(const std::string &key) const {
return params.find(key) != params.end();
}
@ -6571,6 +6640,23 @@ inline void Response::set_header(const std::string &key,
headers.emplace(key, val);
}
}
inline bool Response::has_trailer(const std::string &key) const {
return trailers.find(key) != trailers.end();
}
inline std::string Response::get_trailer_value(const std::string &key,
size_t id) const {
auto rng = trailers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
if (it != rng.second) { return it->second; }
return std::string();
}
inline size_t Response::get_trailer_value_count(const std::string &key) const {
auto r = trailers.equal_range(key);
return static_cast<size_t>(std::distance(r.first, r.second));
}
inline void Response::set_redirect(const std::string &url, int stat) {
if (detail::fields::is_field_value(url)) {