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