src/head_parser.cpp

99.6% Lines (235/236) 100.0% List of functions (17/17) 94.6% Branches (106/112)
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> >&, boost::system::error_code&) :58 79024x 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> >&, boost::system::error_code&) :91 36776x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::parse_version(char const*&, char const*, boost::http::version&, boost::system::error_code&) :124 36023x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::parse_status(char const*&, char const*, unsigned short&, boost::system::error_code&) :151 837x 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> >&, boost::system::error_code&) :192 579x 100.0% 100.0% 100.0% boost::burl::head_parser::head_parser(bool, char*, unsigned long, boost::burl::header_limits const&) :215 40840x 93.8% 83.3% 93.0% boost::burl::head_parser::head_parser(boost::burl::head_parser&&) :240 336x 100.0% 100.0% 100.0% boost::burl::head_parser::operator=(boost::burl::head_parser&&) :255 302x 100.0% 100.0% 100.0% boost::burl::head_parser::ceiling() const :273 195851x 100.0% 100.0% 100.0% boost::burl::head_parser::reset(char*) :284 377x 100.0% 75.0% 92.0% boost::burl::head_parser::rebase(char*) :300 207x 100.0% 50.0% 80.0% boost::burl::head_parser::parse(unsigned long, boost::system::error_code&) :309 92291x 100.0% 62.5% 94.0% auto boost::burl::head_parser::parse(unsigned long, boost::system::error_code&)::{lambda(auto:1&, auto:2, auto:3&)#1}::operator()<char const*, char const*, boost::system::error_code>(char const*&, char const*, boost::system::error_code&) const :326 80679x 100.0% 100.0% 100.0% auto boost::burl::head_parser::parse(unsigned long, boost::system::error_code&)::{lambda(auto:1&, auto:2, auto:3&)#2}::operator()<char const*, char const*, boost::system::error_code>(char const*&, char const*, boost::system::error_code&) const :343 38353x 100.0% 100.0% 100.0% boost::burl::head_parser::parse_start_line_(char const*&, char const*, boost::system::error_code&) :368 80679x 100.0% 100.0% 100.0% boost::burl::head_parser::parse_fields_(char const*&, char const*, boost::system::error_code&) :453 38353x 100.0% 100.0% 100.0% auto boost::burl::head_parser::parse_fields_(char const*&, char const*, boost::system::error_code&)::{lambda(auto:1&, auto:2, auto:3&)#1}::operator()<char const*, char const*, boost::system::error_code>(char const*&, char const*, boost::system::error_code&) const :490 36370x 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 79024x parse_method(
59 char const*& it,
60 char const* end,
61 std::string_view& result,
62 system::error_code& ec)
63 {
64 // parse token SP
65 79024x auto const first = it;
66 114584x for(;; ++it)
67 {
68
2/2
✓ Branch 1 taken 42244 times.
✓ Branch 2 taken 151364 times.
193608x if(distance(it, end) < 1)
69 {
70 42244x ec = error::need_data;
71 42244x return;
72 }
73
2/2
✓ Branch 1 taken 36780 times.
✓ Branch 2 taken 114584 times.
151364x if(! is_token_char(*it))
74 36780x break;
75 }
76
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 36778 times.
36780x if(*it != ' ')
77 {
78 2x ec = error::bad_method;
79 2x return;
80 }
81
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 36776 times.
36778x if(it == first)
82 {
83 // cannot be empty
84 2x ec = error::bad_method;
85 2x return;
86 }
87 36776x result = { first, it++ };
88 }
89
90 void
91 36776x parse_target(
92 char const*& it,
93 char const* end,
94 std::string_view& result,
95 system::error_code& ec)
96 {
97 // parse target SP
98 36776x auto const first = it;
99 83506x for(;; ++it)
100 {
101
2/2
✓ Branch 1 taken 2402 times.
✓ Branch 2 taken 117880 times.
120282x if(distance(it, end) < 1)
102 {
103 2402x ec = error::need_data;
104 2402x return;
105 }
106
2/2
✓ Branch 1 taken 34374 times.
✓ Branch 2 taken 83506 times.
117880x if(! is_target_char(*it))
107 34374x break;
108 }
109
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 34370 times.
34374x 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 34368 times.
34370x if(it == first)
115 {
116 // cannot be empty
117 2x ec = error::bad_request_target;
118 2x return;
119 }
120 34368x result = { first, it++ };
121 }
122
123 void
124 36023x parse_version(
125 char const*& it,
126 char const* end,
127 version& result,
128 system::error_code& ec)
129 {
130
2/2
✓ Branch 1 taken 7267 times.
✓ Branch 2 taken 28756 times.
36023x if(distance(it, end) < 8)
131 {
132 7267x ec = error::need_data;
133 7267x return;
134 }
135
2/2
✓ Branch 0 taken 28739 times.
✓ Branch 1 taken 17 times.
28756x if(std::memcmp(it, "HTTP/1.1", 8) == 0)
136 {
137 28739x it += 8;
138 28739x result = version::http_1_1;
139 28739x 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 837x parse_status(
152 char const*& it,
153 char const* end,
154 std::uint16_t& result,
155 system::error_code& ec)
156 {
157 // parse 3(digit) SP
158
2/2
✓ Branch 1 taken 250 times.
✓ Branch 2 taken 587 times.
837x 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 585 times.
587x if(! is_digit(*it))
164 {
165 2x ec = error::bad_status_code;
166 2x return;
167 }
168 585x result = 100 * (*it++ - '0');
169
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 583 times.
585x if(! is_digit(*it))
170 {
171 2x ec = error::bad_status_code;
172 2x return;
173 }
174 583x result += 10 * (*it++ - '0');
175
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 581 times.
583x if(! is_digit(*it))
176 {
177 2x ec = error::bad_status_code;
178 2x return;
179 }
180 581x result += *it++ - '0';
181
2/2
✓ Branch 0 taken 577 times.
✓ Branch 1 taken 4 times.
581x if(*it == ' ')
182 {
183 577x ++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 579x parse_reason(
193 char const*& it,
194 char const* end,
195 std::string_view& result,
196 system::error_code& ec)
197 {
198 579x auto const first = it;
199 579x char const* token_end = nullptr;
200 579x auto p = parse_token_to_eol(
201 it, end, token_end, ec);
202
2/2
✓ Branch 1 taken 252 times.
✓ Branch 2 taken 327 times.
579x if(ec)
203 254x return;
204
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 325 times.
327x if(! p)
205 {
206 2x ec = error::bad_reason;
207 2x return;
208 }
209 325x result = { first, token_end };
210 325x it = p;
211 }
212
213 } // namespace
214
215 40840x head_parser::
216 head_parser(
217 bool is_request,
218 char* buf,
219 std::size_t n,
220 40840x header_limits const& limits) noexcept
221 40840x : limits_(limits)
222 40840x , is_req_(is_request)
223 {
224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40840 times.
40840x if(limits_.max_size > fields_base::max_buffer_size)
225 limits_.max_size = fields_base::max_buffer_size;
226
227 40840x auto const a = reinterpret_cast<std::uintptr_t>(buf);
228 40840x auto const e = (a + n) -
229 40840x (a + n) % alignof(message_head_base::entry);
230
2/2
✓ Branch 0 taken 40535 times.
✓ Branch 1 taken 305 times.
40840x auto const cap = (e > a) ? e - a : 0;
231
232
2/2
✓ Branch 0 taken 40522 times.
✓ Branch 1 taken 318 times.
40840x if(is_req_)
233 40522x ::new(static_cast<void*>(&s_.req))
234 40522x class request_head_base(buf, cap);
235 else
236 318x ::new(static_cast<void*>(&s_.res))
237 318x class response_head_base(buf, cap);
238 40840x }
239
240 336x head_parser::
241 336x head_parser(head_parser&& other) noexcept
242 336x : limits_(other.limits_)
243 336x , is_req_(other.is_req_)
244 336x , st_(other.st_)
245 {
246
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 325 times.
336x if(is_req_)
247 11x ::new(static_cast<void*>(&s_.req))
248 11x class request_head_base(other.s_.req);
249 else
250 325x ::new(static_cast<void*>(&s_.res))
251 325x class response_head_base(other.s_.res);
252 336x }
253
254 head_parser&
255 302x head_parser::
256 operator=(head_parser&& other) noexcept
257 {
258
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 301 times.
302x if(this == &other)
259 1x return *this;
260 301x limits_ = other.limits_;
261 301x is_req_ = other.is_req_;
262 301x st_ = other.st_;
263
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 298 times.
301x if(is_req_)
264 3x ::new(static_cast<void*>(&s_.req))
265 3x class request_head_base(other.s_.req);
266 else
267 298x ::new(static_cast<void*>(&s_.res))
268 298x class response_head_base(other.s_.res);
269 301x return *this;
270 }
271
272 char*
273 195851x head_parser::
274 ceiling() const noexcept
275 {
276 195851x auto const& h = h_();
277 auto const reserve =
278 195851x message_head_base::table_space_(limits_.max_fields);
279 195851x auto const cap = h.capacity_in_bytes();
280
2/2
✓ Branch 1 taken 195804 times.
✓ Branch 2 taken 47 times.
195851x return h.base_() + (cap > reserve ? cap - reserve : 0);
281 }
282
283 void
284 377x head_parser::
285 reset(char* base) noexcept
286 {
287 377x auto& h = h_();
288
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 377 times.
377x BOOST_ASSERT(base <= ceiling());
289 377x auto const cap = static_cast<std::size_t>(h.end_ - base);
290
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 366 times.
377x if(is_req_)
291 11x ::new(static_cast<void*>(&s_.req))
292 11x class request_head_base(base, cap);
293 else
294 366x ::new(static_cast<void*>(&s_.res))
295 366x class response_head_base(base, cap);
296 377x st_ = state::start_line;
297 377x }
298
299 void
300 207x head_parser::
301 rebase(char* base) noexcept
302 {
303 207x auto& h = h_();
304
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 207 times.
207x BOOST_ASSERT(base <= h.base_());
305 207x h.buf_ = base + h.prefix_;
306 207x }
307
308 void
309 92291x head_parser::
310 parse(
311 std::size_t n,
312 system::error_code& ec) noexcept
313 {
314 92291x ec.clear();
315 92291x auto const& h = h_();
316 92291x char const* it = h.buf_ + h.size_;
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92291 times.
92291x BOOST_ASSERT(n >= std::size_t(h.prefix_) + h.size_);
318
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 92291 times.
92291x BOOST_ASSERT(h.base_() + n <= ceiling());
319 92291x auto* const end = h.base_() + n;
320
321
3/4
✓ Branch 0 taken 80679 times.
✓ Branch 1 taken 11609 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
92291x switch(st_)
322 {
323 80679x case state::start_line:
324 {
325 161358x parse_limited(
326 80679x [this](auto& it, auto end, auto& ec)
327 {
328 80679x parse_start_line_(it, end, ec);
329 80679x },
330 it,
331 end,
332 80679x detail::clamp(limits_.max_start_line, limits_.max_size),
333 error::start_line_limit,
334 ec);
335
2/2
✓ Branch 1 taken 53935 times.
✓ Branch 2 taken 26744 times.
80679x if(ec)
336 53935x break;
337 26744x st_ = state::fields;
338 BOOST_FALLTHROUGH;
339 }
340 38353x case state::fields:
341 {
342 38353x parse_limited(
343 115059x [this](auto& it, auto end, auto& ec)
344 {
345 38353x parse_fields_(it, end, ec);
346 38353x },
347 it,
348 end,
349 38353x limits_.max_size - h.prefix_ - h.size_,
350 error::headers_limit,
351 ec);
352
2/2
✓ Branch 1 taken 29277 times.
✓ Branch 2 taken 9076 times.
38353x if(ec)
353 29277x break;
354 9076x st_ = state::done;
355 BOOST_FALLTHROUGH;
356 }
357 9079x case state::done:
358 {
359 9079x ec = h.validate_framing_();
360 }
361 }
362
363
6/6
✓ Branch 1 taken 51891 times.
✓ Branch 2 taken 40400 times.
✓ Branch 4 taken 214 times.
✓ Branch 5 taken 51677 times.
✓ Branch 6 taken 214 times.
✓ Branch 7 taken 92077 times.
92291x if(ec == error::need_data && end >= ceiling())
364 214x ec = error::in_place_overflow;
365 92291x }
366
367 void
368 80679x head_parser::
369 parse_start_line_(
370 char const*& it,
371 char const* end,
372 system::error_code& ec) noexcept
373 {
374 80679x auto& h = h_();
375 80679x auto const first = it;
376
2/2
✓ Branch 0 taken 79024 times.
✓ Branch 1 taken 1655 times.
80679x if(is_req_)
377 {
378 /*
379 request-line = method SP request-target SP HTTP-version CRLF
380 method = token
381 */
382
383 79024x std::string_view m;
384 79024x parse_method(it, end, m, ec);
385
2/2
✓ Branch 1 taken 42248 times.
✓ Branch 2 taken 36776 times.
79024x if(ec)
386 52605x return;
387
388 36776x std::string_view t;
389 36776x parse_target(it, end, t, ec);
390
2/2
✓ Branch 1 taken 2408 times.
✓ Branch 2 taken 34368 times.
36776x if(ec)
391 2408x return;
392
393 version v;
394 34368x parse_version(it, end, v, ec);
395
2/2
✓ Branch 1 taken 6524 times.
✓ Branch 2 taken 27844 times.
34368x if(ec)
396 6524x return;
397
398
2/2
✓ Branch 1 taken 1419 times.
✓ Branch 2 taken 26425 times.
27844x if(distance(it, end) < 2)
399 {
400 1419x ec = error::need_data;
401 1419x return;
402 }
403
4/4
✓ Branch 0 taken 26421 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 26419 times.
26425x if(it[0] != '\r' || it[1] != '\n')
404 {
405 6x ec = error::bad_line_ending;
406 6x return;
407 }
408 26419x it += 2;
409 26419x h.push_start_line_(
410 26419x m, t, v, distance<std::uint16_t>(first, it));
411 }
412 else
413 {
414 /*
415 status-line = HTTP-version SP status-code SP reason-phrase CRLF
416 status-code = 3*DIGIT
417 reason-phrase = *( HTAB / SP / VCHAR / obs-text )
418 */
419
420 version v;
421 1655x parse_version(it, end, v, ec);
422
2/2
✓ Branch 1 taken 752 times.
✓ Branch 2 taken 903 times.
1655x if(ec)
423 1330x return;
424
425 // SP
426
2/2
✓ Branch 1 taken 64 times.
✓ Branch 2 taken 839 times.
903x if(distance(it, end) < 1)
427 {
428 64x ec = error::need_data;
429 64x return;
430 }
431
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 837 times.
839x if(*it++ != ' ')
432 {
433 2x ec = error::bad_version;
434 2x return;
435 }
436
437 std::uint16_t s;
438 837x parse_status(it, end, s, ec);
439
2/2
✓ Branch 1 taken 258 times.
✓ Branch 2 taken 579 times.
837x if(ec)
440 258x return;
441
442 // parse reason CRLF
443 579x std::string_view r;
444 579x parse_reason(it, end, r, ec);
445
2/2
✓ Branch 1 taken 254 times.
✓ Branch 2 taken 325 times.
579x if(ec)
446 254x return;
447 325x h.push_start_line_(
448 325x v, s, r, distance<std::uint16_t>(first, it));
449 }
450 }
451
452 void
453 38353x head_parser::
454 parse_fields_(
455 char const*& it,
456 char const* end,
457 system::error_code& ec) noexcept
458 {
459 38353x auto& h = h_();
460 38353x std::string_view name;
461 38353x std::string_view value;
462 for(;;)
463 {
464
2/2
✓ Branch 1 taken 2589 times.
✓ Branch 2 taken 53324 times.
55913x if(distance(it, end) < 2)
465 {
466 2589x ec = error::need_data;
467 2589x return;
468 }
469
2/2
✓ Branch 0 taken 9078 times.
✓ Branch 1 taken 44246 times.
53324x if(it[0] == '\r')
470 {
471
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9076 times.
9078x if(it[1] != '\n')
472 {
473 2x ec = error::bad_line_ending;
474 2x return;
475 }
476 // terminating CRLF
477 9076x it += 2;
478 9076x h.size_ += 2;
479 9076x return;
480 }
481
482
2/2
✓ Branch 1 taken 7876 times.
✓ Branch 2 taken 36370 times.
44246x if(h.size() >= limits_.max_fields)
483 {
484 7876x ec = error::fields_limit;
485 7876x return;
486 }
487
488 36370x auto const first = it;
489 36370x parse_limited(
490 109110x [&name, &value](auto& it, auto end, auto& ec)
491 {
492 36370x parse_field(it, end, name, value, ec);
493 36370x },
494 it,
495 end,
496 36370x limits_.max_field + 1u, // 1u for obs lookahead
497 error::field_size_limit,
498 ec);
499
2/2
✓ Branch 1 taken 18776 times.
✓ Branch 2 taken 17594 times.
36370x if(ec)
500 18776x return;
501 ec = h.push_field_(
502 17594x name, value, distance<std::uint16_t>(first, it));
503
2/2
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 17560 times.
17594x if(ec)
504 34x return;
505 17560x }
506 }
507
508 } // namespace burl
509 } // namespace boost
510