Matthew Zipkin
2024-09-30 13:22:29 -04:00
parent 89c54ae4cb
commit 68b5d289d1
3 changed files with 247 additions and 0 deletions

View File

@@ -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