mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-12 21:52:53 +02:00
http: Implement HTTPHeaders class
see: https://www.rfc-editor.org/rfc/rfc2616#section-4.2 https://www.rfc-editor.org/rfc/rfc7231#section-5 https://www.rfc-editor.org/rfc/rfc7231#section-7 https://httpwg.org/specs/rfc9111.html#header.field.definitions
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -714,3 +715,97 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
|
||||
pathHandlers.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
namespace http_bitcoin {
|
||||
|
||||
std::optional<std::string> HTTPHeaders::FindFirst(const std::string_view key) const
|
||||
{
|
||||
for (const auto& item : m_headers) {
|
||||
if (CaseInsensitiveEqual(key, item.first)) {
|
||||
return item.second;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> HTTPHeaders::FindAll(const std::string_view key) const
|
||||
{
|
||||
std::vector<std::string_view> ret;
|
||||
for (const auto& item : m_headers) {
|
||||
if (CaseInsensitiveEqual(key, item.first)) {
|
||||
ret.push_back(item.second);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HTTPHeaders::Write(std::string&& key, std::string&& value)
|
||||
{
|
||||
m_headers.emplace_back(std::move(key), std::move(value));
|
||||
}
|
||||
|
||||
void HTTPHeaders::RemoveAll(std::string_view key)
|
||||
{
|
||||
auto moved = std::ranges::remove_if(m_headers, [key] (auto& pair) {
|
||||
return CaseInsensitiveEqual(key, pair.first);
|
||||
});
|
||||
m_headers.erase(moved.begin(), moved.end());
|
||||
}
|
||||
|
||||
bool HTTPHeaders::Read(util::LineReader& reader)
|
||||
{
|
||||
// Headers https://httpwg.org/specs/rfc9110.html#rfc.section.6.3
|
||||
// A sequence of Field Lines https://httpwg.org/specs/rfc9110.html#rfc.section.5.2
|
||||
while (auto maybe_line = reader.ReadLine()) {
|
||||
if (reader.Consumed() > MAX_HEADERS_SIZE) throw std::runtime_error("HTTP headers exceed size limit");
|
||||
|
||||
const std::string_view& line = *maybe_line;
|
||||
|
||||
// An empty line indicates end of the headers section https://www.rfc-editor.org/rfc/rfc2616#section-4
|
||||
if (line.empty()) return true;
|
||||
|
||||
// "Field values containing CR, LF, or NUL characters are invalid and dangerous"
|
||||
// https://httpwg.org/specs/rfc9110.html#rfc.section.5.5
|
||||
// A sender MUST NOT generate a bare CR (a CR character not immediately followed by LF)
|
||||
// within any protocol elements other than the content.
|
||||
// A recipient of such a bare CR MUST consider that element to be invalid...
|
||||
// https://httpwg.org/specs/rfc9112.html#rfc.section.2.2
|
||||
if (line.find_first_of("\r\n\0", 0, 3) != std::string_view::npos) throw std::runtime_error("Header contains invalid character");
|
||||
|
||||
// Header line must have at least one ":"
|
||||
// keys are not allowed to have delimiters like ":" but values are
|
||||
// https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.2
|
||||
const size_t pos{line.find(':')};
|
||||
if (pos == std::string_view::npos) throw std::runtime_error("HTTP header missing colon (:)");
|
||||
|
||||
// Whitespace is strictly not allowed in the field-name (key)
|
||||
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
|
||||
std::string_view key = line.substr(0, pos);
|
||||
if (key.find_first_of(" \t\n\r\f\v") != std::string_view::npos) throw std::runtime_error("Invalid header field-name contains whitespace");
|
||||
// Whitespace is optional in the value and can be trimmed
|
||||
std::string value = util::TrimString(std::string_view(line).substr(pos + 1));
|
||||
|
||||
// Header keys are Field Names: https://httpwg.org/specs/rfc9110.html#fields.names
|
||||
// which consist of "tokens": https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.2
|
||||
// that can not be empty.
|
||||
if (key.empty()) throw std::runtime_error("Empty HTTP header name");
|
||||
|
||||
Write(std::string(key), std::move(value));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string HTTPHeaders::Stringify() const
|
||||
{
|
||||
std::string out;
|
||||
for (const auto& [key, value] : m_headers) {
|
||||
out += key + ": " + value + "\r\n";
|
||||
}
|
||||
|
||||
// Headers are terminated by an empty line
|
||||
out += "\r\n";
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace http_bitcoin
|
||||
|
||||
Reference in New Issue
Block a user