src/head_parser.cpp

99.6% Lines (238/239) 100.0% List of functions (17/17) 94.7% Branches (108/114)
head_parser.cpp
f(x) Functions (17)
Function Calls Lines Branches Blocks
boost::burl::(anonymous namespace)::parse_method(char const*&, char const*, std::basic_string_view<char, std::char_traits<char> >&, std::error_code&) :58 79027x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::parse_target(char const*&, char const*, std::basic_string_view<char, std::char_traits<char> >&, std::error_code&) :91 36779x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::parse_version(char const*&, char const*, boost::http::version&, std::error_code&) :124 36038x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::parse_status(char const*&, char const*, unsigned short&, std::error_code&) :151 843x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::parse_reason(char const*&, char const*, std::basic_string_view<char, std::char_traits<char> >&, std::error_code&) :192 585x 100.0% 100.0% 100.0% boost::burl::head_parser::head_parser(bool, char*, unsigned long, boost::burl::header_limits const&) :215 40858x 93.8% 83.3% 93.0% boost::burl::head_parser::head_parser(boost::burl::head_parser&&) :240 343x 100.0% 100.0% 100.0% boost::burl::head_parser::operator=(boost::burl::head_parser&&) :255 311x 100.0% 100.0% 100.0% boost::burl::head_parser::ceiling() const :273 195891x 100.0% 100.0% 100.0% boost::burl::head_parser::reset(char*) :284 387x 100.0% 75.0% 92.0% boost::burl::head_parser::rebase(char*) :300 209x 100.0% 50.0% 80.0% boost::burl::head_parser::parse(unsigned long) :309 92306x 100.0% 62.5% 95.0% auto boost::burl::head_parser::parse(unsigned long)::{lambda(auto:1&, auto:2, auto:3&)#1}::operator()<char const*, char const*, std::error_code>(char const*&, char const*, std::error_code&) const :324 80694x 100.0% 100.0% 100.0% auto boost::burl::head_parser::parse(unsigned long)::{lambda(auto:1&, auto:2, auto:3&)#2}::operator()<char const*, char const*, std::error_code>(char const*&, char const*, std::error_code&) const :341 38362x 100.0% 100.0% 100.0% boost::burl::head_parser::parse_start_line_(char const*&, char const*, std::error_code&) :370 80694x 100.0% 100.0% 100.0% boost::burl::head_parser::parse_fields_(char const*&, char const*, std::error_code&) :455 38362x 100.0% 100.0% 100.0% auto boost::burl::head_parser::parse_fields_(char const*&, char const*, std::error_code&)::{lambda(auto:1&, auto:2, auto:3&)#1}::operator()<char const*, char const*, std::error_code>(char const*&, char const*, std::error_code&) const :492 36386x 100.0% 100.0% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2021 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Mohammad Nejati
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/burl
9 //
10
11 #include <boost/burl/head_parser.hpp>
12
13 #include "detail/grammar.hpp"
14 #include "detail/util.hpp"
15
16 #include <boost/assert.hpp>
17
18 #include <cstring>
19 #include <new>
20
21 namespace boost
22 {
23 namespace burl
24 {
25
26 // assert relying facts
27 static_assert(
28 std::is_same_v<
29 decltype(header_limits::max_size), std::uint32_t>);
30
31 static_assert(
32 std::is_same_v<
33 decltype(header_limits::max_fields), std::uint16_t>);
34
35 static_assert(
36 std::is_same_v<
37 decltype(header_limits::max_field), std::uint16_t>);
38
39 static_assert(
40 std::is_same_v<
41 decltype(header_limits::max_start_line), std::uint16_t>);
42
43 using error = http::error;
44 using version = http::version;
45
46 using detail::distance;
47 using detail::is_digit;
48 using detail::is_target_char;
49 using detail::is_token_char;
50 using detail::parse_field;
51 using detail::parse_limited;
52 using detail::parse_token_to_eol;
53
54 namespace
55 {
56
57 void
58 79027x parse_method(
59 char const*& it,
60 char const* end,
61 std::string_view& result,
62 std::error_code& ec)
63 {
64 // parse token SP
65 79027x auto const first = it;
66 114593x for(;; ++it)
67 {
68
2/2
✓ Branch 1 taken 42244 times.
✓ Branch 2 taken 151376 times.
193620x if(distance(it, end) < 1)
69 {
70 42244x ec = error::need_data;
71 42244x return;
72 }
73
2/2
✓ Branch 1 taken 36783 times.
✓ Branch 2 taken 114593 times.
151376x if(! is_token_char(*it))
74 36783x break;
75 }
76
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 36781 times.
36783x if(*it != ' ')
77 {
78 2x ec = error::bad_method;
79 2x return;
80 }
81
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 36779 times.
36781x if(it == first)
82 {
83 // cannot be empty
84 2x ec = error::bad_method;
85 2x return;
86 }
87 36779x result = { first, it++ };
88 }
89
90 void
91 36779x parse_target(
92 char const*& it,
93 char const* end,
94 std::string_view& result,
95 std::error_code& ec)
96 {
97 // parse target SP
98 36779x auto const first = it;
99 83509x for(;; ++it)
100 {
101
2/2
✓ Branch 1 taken 2402 times.
✓ Branch 2 taken 117886 times.
120288x if(distance(it, end) < 1)
102 {
103 2402x ec = error::need_data;
104 2402x return;
105 }
106
2/2
✓ Branch 1 taken 34377 times.
✓ Branch 2 taken 83509 times.
117886x if(! is_target_char(*it))
107 34377x break;
108 }
109
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 34373 times.
34377x if(*it != ' ')
110 {
111 4x ec = error::bad_request_target;
112 4x return;
113 }
114
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 34371 times.
34373x if(it == first)
115 {
116 // cannot be empty
117 2x ec = error::bad_request_target;
118 2x return;
119 }
120 34371x result = { first, it++ };
121 }
122
123 void
124 36038x parse_version(
125 char const*& it,
126 char const* end,
127 version& result,
128 std::error_code& ec)
129 {
130
2/2
✓ Branch 1 taken 7273 times.
✓ Branch 2 taken 28765 times.
36038x if(distance(it, end) < 8)
131 {
132 7273x ec = error::need_data;
133 7273x return;
134 }
135
2/2
✓ Branch 0 taken 28748 times.
✓ Branch 1 taken 17 times.
28765x if(std::memcmp(it, "HTTP/1.1", 8) == 0)
136 {
137 28748x it += 8;
138 28748x result = version::http_1_1;
139 28748x return;
140 }
141
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 9 times.
17x if(std::memcmp(it, "HTTP/1.0", 8) == 0)
142 {
143 8x it += 8;
144 8x result = version::http_1_0;
145 8x return;
146 }
147 9x ec = error::bad_version;
148 }
149
150 void
151 843x parse_status(
152 char const*& it,
153 char const* end,
154 std::uint16_t& result,
155 std::error_code& ec)
156 {
157 // parse 3(digit) SP
158
2/2
✓ Branch 1 taken 250 times.
✓ Branch 2 taken 593 times.
843x if(distance(it, end) < 4)
159 {
160 250x ec = error::need_data;
161 250x return;
162 }
163
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 591 times.
593x if(! is_digit(*it))
164 {
165 2x ec = error::bad_status_code;
166 2x return;
167 }
168 591x result = 100 * (*it++ - '0');
169
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 589 times.
591x if(! is_digit(*it))
170 {
171 2x ec = error::bad_status_code;
172 2x return;
173 }
174 589x result += 10 * (*it++ - '0');
175
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 587 times.
589x if(! is_digit(*it))
176 {
177 2x ec = error::bad_status_code;
178 2x return;
179 }
180 587x result += *it++ - '0';
181
2/2
✓ Branch 0 taken 583 times.
✓ Branch 1 taken 4 times.
587x if(*it == ' ')
182 {
183 583x ++it;
184 }
185
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4x else if(*it != '\r')
186 {
187 2x ec = error::bad_status_code;
188 }
189 }
190
191 void
192 585x parse_reason(
193 char const*& it,
194 char const* end,
195 std::string_view& result,
196 std::error_code& ec)
197 {
198 585x auto const first = it;
199 585x char const* token_end = nullptr;
200 585x auto p = parse_token_to_eol(
201 it, end, token_end, ec);
202
2/2
✓ Branch 1 taken 252 times.
✓ Branch 2 taken 333 times.
585x if(ec)
203 254x return;
204
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 331 times.
333x if(! p)
205 {
206 2x ec = error::bad_reason;
207 2x return;
208 }
209 331x result = { first, token_end };
210 331x it = p;
211 }
212
213 } // namespace
214
215 40858x head_parser::
216 head_parser(
217 bool is_request,
218 char* buf,
219 std::size_t n,
220 40858x header_limits const& limits) noexcept
221 40858x : limits_(limits)
222 40858x , is_req_(is_request)
223 {
224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40858 times.
40858x if(limits_.max_size > fields_base::max_buffer_size)
225 ✗ limits_.max_size = fields_base::max_buffer_size;
226
227 40858x auto const a = reinterpret_cast<std::uintptr_t>(buf);
228 40858x auto const e = (a + n) -
229 40858x (a + n) % alignof(message_head_base::entry);
230
2/2
✓ Branch 0 taken 40544 times.
✓ Branch 1 taken 314 times.
40858x auto const cap = (e > a) ? e - a : 0;
231
232
2/2
✓ Branch 0 taken 40534 times.
✓ Branch 1 taken 324 times.
40858x if(is_req_)
233 40534x ::new(static_cast<void*>(&s_.req))
234 40534x class request_head_base(buf, cap);
235 else
236 324x ::new(static_cast<void*>(&s_.res))
237 324x class response_head_base(buf, cap);
238 40858x }
239
240 343x head_parser::
241 343x head_parser(head_parser&& other) noexcept
242 343x : limits_(other.limits_)
243 343x , is_req_(other.is_req_)
244 343x , st_(other.st_)
245 {
246
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 332 times.
343x if(is_req_)
247 11x ::new(static_cast<void*>(&s_.req))
248 11x class request_head_base(other.s_.req);
249 else
250 332x ::new(static_cast<void*>(&s_.res))
251 332x class response_head_base(other.s_.res);
252 343x }
253
254 head_parser&
255 311x head_parser::
256 operator=(head_parser&& other) noexcept
257 {
258
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 310 times.
311x if(this == &other)
259 1x return *this;
260 310x limits_ = other.limits_;
261 310x is_req_ = other.is_req_;
262 310x st_ = other.st_;
263
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 304 times.
310x if(is_req_)
264 6x ::new(static_cast<void*>(&s_.req))
265 6x class request_head_base(other.s_.req);
266 else
267 304x ::new(static_cast<void*>(&s_.res))
268 304x class response_head_base(other.s_.res);
269 310x return *this;
270 }
271
272 char*
273 195891x head_parser::
274 ceiling() const noexcept
275 {
276 195891x auto const& h = h_();
277 auto const reserve =
278 195891x message_head_base::table_space_(limits_.max_fields);
279 195891x auto const cap = h.capacity_in_bytes();
280
2/2
✓ Branch 1 taken 195844 times.
✓ Branch 2 taken 47 times.
195891x return h.base_() + (cap > reserve ? cap - reserve : 0);
281 }
282
283 void
284 387x head_parser::
285 reset(char* base) noexcept
286 {
287 387x auto& h = h_();
288
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 387 times.
387x BOOST_ASSERT(base <= ceiling());
289 387x auto const cap = static_cast<std::size_t>(h.end_ - base);
290
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 373 times.
387x if(is_req_)
291 14x ::new(static_cast<void*>(&s_.req))
292 14x class request_head_base(base, cap);
293 else
294 373x ::new(static_cast<void*>(&s_.res))
295 373x class response_head_base(base, cap);
296 387x st_ = state::start_line;
297 387x }
298
299 void
300 209x head_parser::
301 rebase(char* base) noexcept
302 {
303 209x auto& h = h_();
304
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 209 times.
209x BOOST_ASSERT(base <= h.base_());
305 209x h.buf_ = base + h.prefix_;
306 209x }
307
308 system::result<void, std::error_code>
309 92306x head_parser::
310 parse(std::size_t n) noexcept
311 {
312 92306x std::error_code ec;
313 92306x auto const& h = h_();
314 92306x char const* it = h.buf_ + h.size_;
315
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92306 times.
92306x BOOST_ASSERT(n >= std::size_t(h.prefix_) + h.size_);
316
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 92306 times.
92306x BOOST_ASSERT(h.base_() + n <= ceiling());
317 92306x auto* const end = h.base_() + n;
318
319
3/4
✓ Branch 0 taken 80694 times.
✓ Branch 1 taken 11609 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
92306x switch(st_)
320 {
321 80694x case state::start_line:
322 {
323 161388x parse_limited(
324 80694x [this](auto& it, auto end, auto& ec)
325 {
326 80694x parse_start_line_(it, end, ec);
327 80694x },
328 it,
329 end,
330 80694x detail::clamp(limits_.max_start_line, limits_.max_size),
331 error::start_line_limit,
332 ec);
333
2/2
✓ Branch 1 taken 53941 times.
✓ Branch 2 taken 26753 times.
80694x if(ec)
334 53941x break;
335 26753x st_ = state::fields;
336 BOOST_FALLTHROUGH;
337 }
338 38362x case state::fields:
339 {
340 38362x parse_limited(
341 115086x [this](auto& it, auto end, auto& ec)
342 {
343 38362x parse_fields_(it, end, ec);
344 38362x },
345 it,
346 end,
347 38362x limits_.max_size - h.prefix_ - h.size_,
348 error::headers_limit,
349 ec);
350
2/2
✓ Branch 1 taken 29277 times.
✓ Branch 2 taken 9085 times.
38362x if(ec)
351 29277x break;
352 9085x st_ = state::done;
353 BOOST_FALLTHROUGH;
354 }
355 9088x case state::done:
356 {
357 9088x ec = h.validate_framing_();
358 }
359 }
360
361
6/6
✓ Branch 2 taken 51897 times.
✓ Branch 3 taken 40409 times.
✓ Branch 5 taken 214 times.
✓ Branch 6 taken 51683 times.
✓ Branch 7 taken 214 times.
✓ Branch 8 taken 92092 times.
92306x if(ec == error::need_data && end >= ceiling())
362 214x ec = error::in_place_overflow;
363
364
2/2
✓ Branch 1 taken 83224 times.
✓ Branch 2 taken 9082 times.
92306x if(ec)
365 83224x return ec;
366 9082x return {};
367 }
368
369 void
370 80694x head_parser::
371 parse_start_line_(
372 char const*& it,
373 char const* end,
374 std::error_code& ec) noexcept
375 {
376 80694x auto& h = h_();
377 80694x auto const first = it;
378
2/2
✓ Branch 0 taken 79027 times.
✓ Branch 1 taken 1667 times.
80694x if(is_req_)
379 {
380 /*
381 request-line = method SP request-target SP HTTP-version CRLF
382 method = token
383 */
384
385 79027x std::string_view m;
386 79027x parse_method(it, end, m, ec);
387
2/2
✓ Branch 1 taken 42248 times.
✓ Branch 2 taken 36779 times.
79027x if(ec)
388 52605x return;
389
390 36779x std::string_view t;
391 36779x parse_target(it, end, t, ec);
392
2/2
✓ Branch 1 taken 2408 times.
✓ Branch 2 taken 34371 times.
36779x if(ec)
393 2408x return;
394
395 version v;
396 34371x parse_version(it, end, v, ec);
397
2/2
✓ Branch 1 taken 6524 times.
✓ Branch 2 taken 27847 times.
34371x if(ec)
398 6524x return;
399
400
2/2
✓ Branch 1 taken 1419 times.
✓ Branch 2 taken 26428 times.
27847x if(distance(it, end) < 2)
401 {
402 1419x ec = error::need_data;
403 1419x return;
404 }
405
4/4
✓ Branch 0 taken 26424 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 26422 times.
26428x if(it[0] != '\r' || it[1] != '\n')
406 {
407 6x ec = error::bad_line_ending;
408 6x return;
409 }
410 26422x it += 2;
411 26422x h.push_start_line_(
412 26422x m, t, v, distance<std::uint16_t>(first, it));
413 }
414 else
415 {
416 /*
417 status-line = HTTP-version SP status-code SP reason-phrase CRLF
418 status-code = 3*DIGIT
419 reason-phrase = *( HTAB / SP / VCHAR / obs-text )
420 */
421
422 version v;
423 1667x parse_version(it, end, v, ec);
424
2/2
✓ Branch 1 taken 758 times.
✓ Branch 2 taken 909 times.
1667x if(ec)
425 1336x return;
426
427 // SP
428
2/2
✓ Branch 1 taken 64 times.
✓ Branch 2 taken 845 times.
909x if(distance(it, end) < 1)
429 {
430 64x ec = error::need_data;
431 64x return;
432 }
433
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 843 times.
845x if(*it++ != ' ')
434 {
435 2x ec = error::bad_version;
436 2x return;
437 }
438
439 std::uint16_t s;
440 843x parse_status(it, end, s, ec);
441
2/2
✓ Branch 1 taken 258 times.
✓ Branch 2 taken 585 times.
843x if(ec)
442 258x return;
443
444 // parse reason CRLF
445 585x std::string_view r;
446 585x parse_reason(it, end, r, ec);
447
2/2
✓ Branch 1 taken 254 times.
✓ Branch 2 taken 331 times.
585x if(ec)
448 254x return;
449 331x h.push_start_line_(
450 331x v, s, r, distance<std::uint16_t>(first, it));
451 }
452 }
453
454 void
455 38362x head_parser::
456 parse_fields_(
457 char const*& it,
458 char const* end,
459 std::error_code& ec) noexcept
460 {
461 38362x auto& h = h_();
462 38362x std::string_view name;
463 38362x std::string_view value;
464 for(;;)
465 {
466
2/2
✓ Branch 1 taken 2589 times.
✓ Branch 2 taken 53349 times.
55938x if(distance(it, end) < 2)
467 {
468 2589x ec = error::need_data;
469 2589x return;
470 }
471
2/2
✓ Branch 0 taken 9087 times.
✓ Branch 1 taken 44262 times.
53349x if(it[0] == '\r')
472 {
473
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9085 times.
9087x if(it[1] != '\n')
474 {
475 2x ec = error::bad_line_ending;
476 2x return;
477 }
478 // terminating CRLF
479 9085x it += 2;
480 9085x h.size_ += 2;
481 9085x return;
482 }
483
484
2/2
✓ Branch 1 taken 7876 times.
✓ Branch 2 taken 36386 times.
44262x if(h.size() >= limits_.max_fields)
485 {
486 7876x ec = error::fields_limit;
487 7876x return;
488 }
489
490 36386x auto const first = it;
491 36386x parse_limited(
492 109158x [&name, &value](auto& it, auto end, auto& ec)
493 {
494 36386x parse_field(it, end, name, value, ec);
495 36386x },
496 it,
497 end,
498 36386x limits_.max_field + 1u, // 1u for obs lookahead
499 error::field_size_limit,
500 ec);
501
2/2
✓ Branch 1 taken 18776 times.
✓ Branch 2 taken 17610 times.
36386x if(ec)
502 18776x return;
503 17610x ec = h.push_field_(
504 17610x name, value, distance<std::uint16_t>(first, it));
505
2/2
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 17576 times.
17610x if(ec)
506 34x return;
507 17576x }
508 }
509
510 } // namespace burl
511 } // namespace boost
512