/src/pistache/src/common/http_headers.cc
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * SPDX-FileCopyrightText: 2015 Mathieu Stefani |
3 | | * |
4 | | * SPDX-License-Identifier: Apache-2.0 |
5 | | */ |
6 | | |
7 | | /* http_headers.cc |
8 | | Mathieu Stefani, 19 August 2015 |
9 | | |
10 | | Headers registry |
11 | | */ |
12 | | |
13 | | #include <pistache/http_headers.h> |
14 | | |
15 | | #include <memory> |
16 | | #include <stdexcept> |
17 | | #include <unordered_map> |
18 | | #include <vector> |
19 | | |
20 | | namespace Pistache::Http::Header |
21 | | { |
22 | | RegisterHeader(Accept); |
23 | | RegisterHeader(AccessControlAllowOrigin); |
24 | | RegisterHeader(AccessControlAllowHeaders); |
25 | | RegisterHeader(AccessControlExposeHeaders); |
26 | | RegisterHeader(AccessControlAllowMethods); |
27 | | RegisterHeader(Allow); |
28 | | RegisterHeader(CacheControl); |
29 | | RegisterHeader(Connection); |
30 | | RegisterHeader(AcceptEncoding); |
31 | | RegisterHeader(ContentEncoding); |
32 | | RegisterHeader(TransferEncoding); |
33 | | RegisterHeader(ContentLength); |
34 | | RegisterHeader(ContentType); |
35 | | RegisterHeader(Authorization); |
36 | | RegisterHeader(Date); |
37 | | RegisterHeader(Expect); |
38 | | RegisterHeader(Host); |
39 | | RegisterHeader(LastModified); |
40 | | RegisterHeader(Location); |
41 | | RegisterHeader(Server); |
42 | | RegisterHeader(UserAgent); |
43 | | |
44 | | bool strToQvalue(const char* str, float* qvalue, std::size_t* qvalueLen) |
45 | 13.1k | { |
46 | 13.1k | constexpr char offset = '0'; |
47 | | |
48 | 13.1k | *qvalueLen = 0; |
49 | | |
50 | | // It is useless to read more than 6 chars, as the maximum allowed |
51 | | // number of digits after the dot is 3, so n.nnn is 5. |
52 | | // The 6th character is read to check if the user specified a qvalue |
53 | | // with too many digits. |
54 | 41.0k | for (; *qvalueLen < 6; (*qvalueLen)++) |
55 | 40.6k | { |
56 | | // the decimal dot is only allowed at index 1; |
57 | | // 0.15 ok |
58 | | // 1.10 ok |
59 | | // 1.0.1 no |
60 | | // .40 no |
61 | 40.6k | if (str[*qvalueLen] == '.' && *qvalueLen != 1) |
62 | 919 | { |
63 | 919 | return false; |
64 | 919 | } |
65 | | |
66 | | // The only valid characters are digits and the decimal dot, |
67 | | // anything else signals the end of the string |
68 | 39.7k | if (str[*qvalueLen] != '.' && !std::isdigit(str[*qvalueLen])) |
69 | 11.9k | { |
70 | 11.9k | break; |
71 | 11.9k | } |
72 | 39.7k | } |
73 | | |
74 | | // Guards against numbers like: |
75 | | // empty |
76 | | // 1. |
77 | | // 0.1234 |
78 | 12.2k | if (*qvalueLen < 1 || *qvalueLen == 2 || *qvalueLen > 5) |
79 | 778 | { |
80 | 778 | return false; |
81 | 778 | } |
82 | | |
83 | | // The first char can only be 0 or 1 |
84 | 11.4k | if (str[0] != '0' && str[0] != '1') |
85 | 245 | { |
86 | 245 | return false; |
87 | 245 | } |
88 | | |
89 | 11.2k | int qint = 0; |
90 | | |
91 | 11.2k | switch (*qvalueLen) |
92 | 11.2k | { |
93 | 584 | case 5: |
94 | 584 | qint += (str[4] - offset); |
95 | 584 | [[fallthrough]]; |
96 | 2.41k | case 4: |
97 | 2.41k | qint += (str[3] - offset) * 10; |
98 | 2.41k | [[fallthrough]]; |
99 | 3.81k | case 3: |
100 | 3.81k | qint += (str[2] - offset) * 100; |
101 | 3.81k | [[fallthrough]]; |
102 | 11.2k | case 1: |
103 | 11.2k | qint += (str[0] - offset) * 1000; |
104 | 11.2k | } |
105 | | |
106 | 11.2k | *qvalue = static_cast<short>(qint) / 1000.0F; |
107 | | |
108 | 11.2k | if (*qvalue > 1) |
109 | 45 | { |
110 | 45 | return false; |
111 | 45 | } |
112 | | |
113 | 11.2k | return true; |
114 | 11.2k | } |
115 | | |
116 | | std::string toLowercase(std::string str) |
117 | 134k | { |
118 | 134k | std::transform(str.begin(), str.end(), str.begin(), |
119 | 134k | [](const char ch) |
120 | 1.33M | { |
121 | 1.33M | const unsigned char uch = |
122 | 1.33M | static_cast<unsigned char>(ch); |
123 | 1.33M | auto ires = ::tolower(uch); |
124 | 1.33M | return(static_cast<char>(ires)); |
125 | 1.33M | }); |
126 | | // Note re: the lambda function being used for std::transform |
127 | | // above. Previously (before 10/2024), std::transform was simply being |
128 | | // passed ::tolower/::toupper for the transformer function, but in fact |
129 | | // we need to make two changes to that: i) we need to cast the input |
130 | | // parm to "unsigned char" to avoid errors due to sign extension as |
131 | | // explained on the Linux man page |
132 | | // (e.g. https://www.man7.org/linux/man-pages/man3/toupper.3.html, |
133 | | // Notes section); and ii) since the result of the transformer function |
134 | | // is written into std::string, i.e. to a char, the transformer |
135 | | // function needs to return a char, not (as ::tolower/::toupper does) |
136 | | // an int, otherwise the compiler may complain about loss of integer |
137 | | // size in writing the int to a char (and in fact MSVC was complaining |
138 | | // in exactly this way). |
139 | 134k | return str; |
140 | 134k | } |
141 | | |
142 | | bool LowercaseEqualStatic(const std::string& dynamic, |
143 | | const std::string& statik) |
144 | 73.3k | { |
145 | 73.3k | return std::equal( |
146 | 73.3k | dynamic.begin(), dynamic.end(), statik.begin(), statik.end(), |
147 | 73.3k | [](const char& a, const char& b) { return std::tolower(a) == b; }); |
148 | 73.3k | } |
149 | | |
150 | | Registry& Registry::instance() |
151 | 54.8k | { |
152 | 54.8k | static Registry instance; |
153 | | |
154 | 54.8k | return instance; |
155 | 54.8k | } |
156 | | |
157 | 2 | Registry::Registry() = default; |
158 | | |
159 | 0 | Registry::~Registry() = default; |
160 | | |
161 | | void Registry::registerHeader(const std::string& name, |
162 | | Registry::RegistryFunc func) |
163 | 42 | { |
164 | 42 | auto it = registry.find(name); |
165 | 42 | if (it != std::end(registry)) |
166 | 0 | { |
167 | 0 | throw std::runtime_error("Header already registered"); |
168 | 0 | } |
169 | | |
170 | 42 | registry.insert(std::make_pair(name, std::move(func))); |
171 | 42 | } |
172 | | |
173 | | std::vector<std::string> Registry::headersList() |
174 | 0 | { |
175 | 0 | std::vector<std::string> names; |
176 | 0 | names.reserve(registry.size()); |
177 | |
|
178 | 0 | for (const auto& header : registry) |
179 | 0 | { |
180 | 0 | names.push_back(header.first); |
181 | 0 | } |
182 | |
|
183 | 0 | return names; |
184 | 0 | } |
185 | | |
186 | | std::unique_ptr<Header> Registry::makeHeader(const std::string& name) |
187 | 20.1k | { |
188 | 20.1k | auto it = registry.find(name); |
189 | 20.1k | if (it == std::end(registry)) |
190 | 0 | { |
191 | 0 | throw std::runtime_error("Unknown header"); |
192 | 0 | } |
193 | | |
194 | 20.1k | return it->second(); |
195 | 20.1k | } |
196 | | |
197 | | bool Registry::isRegistered(const std::string& name) |
198 | 34.6k | { |
199 | 34.6k | auto it = registry.find(name); |
200 | 34.6k | return it != std::end(registry); |
201 | 34.6k | } |
202 | | |
203 | | Collection& Collection::add(const std::shared_ptr<Header>& header) |
204 | 19.9k | { |
205 | 19.9k | headers.insert(std::make_pair(header->name(), header)); |
206 | | |
207 | 19.9k | return *this; |
208 | 19.9k | } |
209 | | |
210 | | Collection& Collection::addRaw(const Raw& raw) |
211 | 38.5k | { |
212 | 38.5k | rawHeaders.insert(std::make_pair(raw.name(), raw)); |
213 | 38.5k | return *this; |
214 | 38.5k | } |
215 | | |
216 | | std::shared_ptr<const Header> Collection::get(const std::string& name) const |
217 | 0 | { |
218 | 0 | auto header = getImpl(name); |
219 | 0 | if (!header.first) |
220 | 0 | { |
221 | 0 | throw std::runtime_error("Could not find header"); |
222 | 0 | } |
223 | | |
224 | 0 | return header.second; |
225 | 0 | } |
226 | | |
227 | | std::shared_ptr<Header> Collection::get(const std::string& name) |
228 | 0 | { |
229 | 0 | auto header = getImpl(name); |
230 | 0 | if (!header.first) |
231 | 0 | { |
232 | 0 | throw std::runtime_error("Could not find header"); |
233 | 0 | } |
234 | | |
235 | 0 | return header.second; |
236 | 0 | } |
237 | | |
238 | | Raw Collection::getRaw(const std::string& name) const |
239 | 0 | { |
240 | 0 | auto it = rawHeaders.find(name); |
241 | 0 | if (it == std::end(rawHeaders)) |
242 | 0 | { |
243 | 0 | throw std::runtime_error("Could not find header"); |
244 | 0 | } |
245 | | |
246 | 0 | return it->second; |
247 | 0 | } |
248 | | |
249 | | std::shared_ptr<const Header> |
250 | | Collection::tryGet(const std::string& name) const |
251 | 0 | { |
252 | 0 | auto header = getImpl(name); |
253 | 0 | if (!header.first) |
254 | 0 | return nullptr; |
255 | | |
256 | 0 | return header.second; |
257 | 0 | } |
258 | | |
259 | | std::shared_ptr<Header> Collection::tryGet(const std::string& name) |
260 | 1.58k | { |
261 | 1.58k | auto header = getImpl(name); |
262 | 1.58k | if (!header.first) |
263 | 880 | return nullptr; |
264 | | |
265 | 704 | return header.second; |
266 | 1.58k | } |
267 | | |
268 | | std::optional<Raw> Collection::tryGetRaw(const std::string& name) const |
269 | 0 | { |
270 | 0 | auto it = rawHeaders.find(name); |
271 | 0 | if (it == std::end(rawHeaders)) |
272 | 0 | { |
273 | 0 | return std::nullopt; |
274 | 0 | } |
275 | | |
276 | 0 | return std::optional<Raw>(it->second); |
277 | 0 | } |
278 | | |
279 | | bool Collection::has(const std::string& name) const |
280 | 0 | { |
281 | 0 | return getImpl(name).first; |
282 | 0 | } |
283 | | |
284 | | std::vector<std::shared_ptr<Header>> Collection::list() const |
285 | 0 | { |
286 | 0 | std::vector<std::shared_ptr<Header>> ret; |
287 | 0 | ret.reserve(headers.size()); |
288 | 0 | for (const auto& h : headers) |
289 | 0 | { |
290 | 0 | ret.push_back(h.second); |
291 | 0 | } |
292 | |
|
293 | 0 | return ret; |
294 | 0 | } |
295 | | |
296 | | bool Collection::remove(const std::string& name) |
297 | 0 | { |
298 | 0 | auto tit = headers.find(name); |
299 | 0 | if (tit == std::end(headers)) |
300 | 0 | { |
301 | 0 | auto rit = rawHeaders.find(name); |
302 | 0 | if (rit == std::end(rawHeaders)) |
303 | 0 | return false; |
304 | | |
305 | 0 | rawHeaders.erase(rit); |
306 | 0 | return true; |
307 | 0 | } |
308 | 0 | headers.erase(tit); |
309 | 0 | return true; |
310 | 0 | } |
311 | | |
312 | | void Collection::clear() |
313 | 0 | { |
314 | 0 | headers.clear(); |
315 | 0 | rawHeaders.clear(); |
316 | 0 | } |
317 | | |
318 | | std::pair<bool, std::shared_ptr<Header>> |
319 | | Collection::getImpl(const std::string& name) const |
320 | 1.58k | { |
321 | 1.58k | auto it = headers.find(name); |
322 | 1.58k | if (it == std::end(headers)) |
323 | 880 | { |
324 | 880 | return std::make_pair(false, nullptr); |
325 | 880 | } |
326 | | |
327 | 704 | return std::make_pair(true, it->second); |
328 | 1.58k | } |
329 | | |
330 | | } // namespace Pistache::Http::Header |