userver: userver/http/predefined_header.hpp Source File
Loading...
Searching...
No Matches
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};