userver: userver/http/predefined_header.hpp Source File
Loading...
Searching...
No Matches
predefined_header.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/http/predefined_header.hpp
4/// @brief Predefined HTTP header names and related helpers.
5/// @ingroup userver_universal
6
7#include <string>
8#include <string_view>
9
10#include <fmt/core.h>
11
12#include <userver/utils/small_string_fwd.hpp>
13#include <userver/utils/string_literal.hpp>
14#include <userver/utils/trivial_map.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace http::headers {
19
20// According to https://www.chromium.org/spdy/spdy-whitepaper/
21// "typical header sizes of 700-800 bytes is common"
22inline constexpr std::size_t kTypicalHeadersSize = 1024;
23using HeadersString = utils::SmallString<kTypicalHeadersSize>;
24
25namespace impl {
26
27// This is a constexpr implementation of case-insensitive MurMur hash for 64-bit
28// size_t and Little Endianness.
29// https://github.com/gcc-mirror/gcc/blob/65369ab62cee68eb7f6ef65e3d12d1969a9e20ee/libstdc%2B%2B-v3/libsupc%2B%2B/hash_bytes.cc#L138
30//
31// P.S. The hasher is "unsafe" in hash-flood sense.
32struct UnsafeConstexprHasher final {
33 constexpr std::size_t operator()(std::string_view str) const noexcept {
34 constexpr std::uint64_t mul = (0xc6a4a793UL << 32UL) + 0x5bd1e995UL;
35
36 std::uint64_t hash = seed_ ^ (str.size() * mul);
37 while (str.size() >= 8) {
38 const std::uint64_t data = ShiftMix(Load8(str.data()) * mul) * mul;
39 hash ^= data;
40 hash *= mul;
41
42 str = str.substr(8);
43 }
44 if (!str.empty()) {
45 const std::uint64_t data = LoadN(str.data(), str.size());
46 hash ^= data;
47 hash *= mul;
48 }
49
50 hash = ShiftMix(hash) * mul;
51 hash = ShiftMix(hash);
52 return hash;
53 }
54
55private:
56 static constexpr inline std::uint64_t ShiftMix(std::uint64_t v) noexcept { return v ^ (v >> 47); }
57
58 static constexpr inline std::uint64_t Load8(const char* data) noexcept { return LoadN(data, 8); }
59
60 static constexpr inline std::uint64_t LoadN(const char* data, std::size_t n) noexcept {
61 // Although lowercase and uppercase ASCII are indeed 32 (0x20) apart,
62 // this approach makes for instance '[' and '{' equivalent too,
63 // which is obviously broken and is easily exploitable.
64 // However, for expected input (lower/upper-case ASCII letters + dashes)
65 // this just works, and against malicious
66 // input we defend by falling back to case-insensitive SipHash.
67 constexpr std::uint64_t kDeliberatelyBrokenLowercaseMask = 0x2020202020202020UL;
68
69 std::uint64_t result = kDeliberatelyBrokenLowercaseMask >> (8 * (8 - n));
70 for (std::size_t i = 0; i < n; ++i) {
71 const std::uint8_t c = data[i];
72 result |= static_cast<std::uint64_t>(c) << (8 * i);
73 }
74 return result;
75 }
76
77 // Seed is chosen in such a way that 16 (presumably) most common userver
78 // headers don't collide within default size of HeaderMap (32),
79 // and that all headers used in userver itself don't collide within minimal
80 // size needed to store them all (36 headers, minimal size = 64).
81 // Note that since HeaderMap takes hashes modulo power of 2 it's guaranteed
82 // that if two headers don't collide within size S, they don't collide for
83 // bigger sizes.
84 std::uint64_t seed_{54999};
85};
86
87// Ugly, but TrivialBiMap requires keys to be in lower case.
88inline constexpr utils::TrivialBiMap kKnownHeadersLowercaseMap = [](auto selector) {
89 return selector()
90 .Case("content-type", std::int8_t{1})
91 .Case("content-encoding", 2)
92 .Case("content-length", 3)
93 .Case("transfer-encoding", 4)
94 .Case("host", 5)
95 .Case("accept", 6)
96 .Case("accept-encoding", 7)
97 .Case("accept-language", 8)
98 .Case("x-yataxi-api-key", 9)
99 .Case("user-agent", 10)
100 .Case("x-request-application", 11)
101 .Case("date", 12)
102 .Case("warning", 13)
103 .Case("access-control-allow-headers", 14)
104 .Case("allow", 15)
105 .Case("server", 16)
106 .Case("set-cookie", 17)
107 .Case("connection", 18)
108 .Case("cookie", 19)
109 .Case("x-yarequestid", 20)
110 .Case("x-yatraceid", 21)
111 .Case("x-yaspanid", 22)
112 .Case("x-requestid", 23)
113 .Case("x-backend-server", 24)
114 .Case("x-taxi-envoyproxy-dstvhost", 25)
115 .Case("baggage", 26)
116 .Case("x-yataxi-allow-auth-request", 27)
117 .Case("x-yataxi-allow-auth-response", 28)
118 .Case("x-yataxi-server-hostname", 29)
119 .Case("x-yataxi-client-timeoutms", 30)
120 .Case("x-yataxi-deadline-expired", 31)
121 .Case("x-yataxi-ratelimited-by", 32)
122 .Case("x-yataxi-ratelimit-reason", 33)
123 .Case("x-b3-traceid", 34)
124 .Case("x-b3-spanid", 35)
125 .Case("x-b3-sampled", 36)
126 .Case("x-b3-parentspanid", 37)
127 .Case("traceparent", 38)
128 .Case("tracestate", 39)
129 .Case("http2-settings", 40)
130 .Case(":method", 41)
131 .Case(":path", 42)
132 .Case("x-request-deadline", 43);
133};
134
135// We use different values for "no index" at compile and run time to simplify
136// comparison - with these values being different we cant just == them.
137inline constexpr std::int8_t kNoHeaderIndexLookup = -1;
138inline constexpr std::int8_t kNoHeaderIndexInsertion = -2;
139static_assert(kNoHeaderIndexLookup != kNoHeaderIndexInsertion);
140static_assert(kNoHeaderIndexLookup != 0 && kNoHeaderIndexInsertion != 0);
141
142// We use this function when constructing a PredefinedHeader ...
143constexpr std::int8_t GetHeaderIndexForLookup(std::string_view key) {
144 const auto opt = kKnownHeadersLowercaseMap.TryFindICaseByFirst(key);
145 return opt.value_or(kNoHeaderIndexLookup);
146}
147
148// And this one when inserting an entry into the HeaderMap.
149// The purpose of having 2 different functions is to be able to
150// == header indexes even if none is present (both headers are unknown).
151inline std::int8_t GetHeaderIndexForInsertion(std::string_view key) {
152 const auto opt = kKnownHeadersLowercaseMap.TryFindICaseByFirst(key);
153 return opt.value_or(kNoHeaderIndexInsertion);
154}
155
156} // namespace impl
157
158namespace header_map {
159class Danger;
160class Map;
161} // namespace header_map
162
163/// @ingroup userver_universal userver_containers
164///
165/// @brief A struct to represent compile-time known header name.
166///
167/// Calculates the hash value at compile time with the same hasher
168/// HeaderMap uses, which allows to speed things up greatly.
169///
170/// Although it's possible to construct PredefinedHeader at runtime
171/// it makes little sense and is error-prone, since it
172/// doesn't own its data, so don't do that until really needed.
173class PredefinedHeader final {
174public:
175 explicit constexpr PredefinedHeader(utils::StringLiteral name)
176 : name_{name},
177 hash_{impl::UnsafeConstexprHasher{}(name)},
178 header_index_{impl::GetHeaderIndexForLookup(name)}
179 {}
180
181 constexpr operator utils::StringLiteral() const { return name_; }
182
183 constexpr operator utils::zstring_view() const { return name_; }
184
185 constexpr operator std::string_view() const { return name_; }
186
187 explicit operator std::string() const { return std::string{name_}; }
188
189private:
190 friend class header_map::Danger;
191 friend class header_map::Map;
192
193 // Header name.
194 const utils::StringLiteral name_;
195
196 // Unsafe constexpr hash (unsafe in a hash-flood sense).
197 const std::size_t hash_;
198
199 // We assign a different 'index' value to every known header,
200 // which allows us to do not perform case-insensitive names compare if indexes
201 // match.
202 // With this trick a successful lookup for PredefinedHeader in HeaderMap
203 // is basically "access an array by index and compare both hash and index".
204 // You can think of this field as an enum discriminant.
205 const std::int8_t header_index_;
206};
207
208} // namespace http::headers
209
210USERVER_NAMESPACE_END
211
212template <>
213struct fmt::formatter<USERVER_NAMESPACE::http::headers::PredefinedHeader> : fmt::formatter<std::string_view> {
214 template <typename FormatContext>
215 auto format(const USERVER_NAMESPACE::http::headers::PredefinedHeader& value, FormatContext& ctx) const
216 -> decltype(ctx.out()) {
217 return fmt::format_to(ctx.out(), "{}", std::string_view{value});
218 }
219};