src/detail/parser.cpp

96.5% Lines (384/398) 100.0% List of functions (51/51) 86.7% Branches (170/196)
parser.cpp
f(x) Functions (51)
Function Calls Lines Branches Blocks
boost::burl::detail::(anonymous namespace)::chained_sequence::chained_sequence(std::array<boost::capy::const_buffer, 2ul> const&) :56 198x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::chained_sequence(char const*, unsigned long) :64 61x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::next() :73 1115x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::advance(unsigned long) :95 87x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::empty() const :111 1030x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::value() const :117 1194x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::pos() const :123 20x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::size() const :129 606x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::chained_sequence::prefix(unsigned long) const :135 170x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::skip_to_eol(boost::burl::detail::(anonymous namespace)::chained_sequence&) :146 171x 90.9% 87.5% 86.0% boost::burl::detail::(anonymous namespace)::parse_chunk_header(boost::burl::detail::(anonymous namespace)::chained_sequence&, unsigned long&) :165 177x 92.9% 87.5% 87.0% boost::burl::detail::(anonymous namespace)::skip_trailer(boost::burl::detail::(anonymous namespace)::chained_sequence&) :190 58x 92.3% 90.0% 89.0% boost::burl::detail::(anonymous namespace)::skip_crlf(boost::burl::detail::(anonymous namespace)::chained_sequence&) :212 209x 85.7% 62.5% 80.0% boost::burl::detail::(anonymous namespace)::collect(std::span<boost::capy::const_buffer, 18446744073709551615ul>, std::array<boost::capy::const_buffer, 2ul> const&, unsigned long) :223 60x 100.0% 75.0% 100.0% boost::burl::detail::(anonymous namespace)::move_leftovers(char*, std::array<boost::capy::const_buffer, 2ul> const&) :241 208x 50.0% 25.0% 73.0% decltype ({parm#1}) boost::burl::detail::(anonymous namespace)::prefix<boost::capy::mutable_buffer>(boost::capy::mutable_buffer, unsigned long) :267 25x 100.0% 100.0% boost::burl::detail::parser::chunk_fn::chunk_fn<boost::burl::detail::parser::consume(unsigned long)::{lambda(boost::capy::const_buffer, bool)#1}>(boost::burl::detail::parser::consume(unsigned long)::{lambda(boost::capy::const_buffer, bool)#1}&&) :283 26x 100.0% 100.0% boost::burl::detail::parser::chunk_fn::chunk_fn<boost::burl::detail::parser::decode_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}&>(boost::burl::detail::parser::decode_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}&) :283 5x 100.0% 100.0% boost::burl::detail::parser::chunk_fn::chunk_fn<boost::burl::detail::parser::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}>(boost::burl::detail::parser::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}&&) :283 93x 100.0% 100.0% boost::burl::detail::parser::chunk_fn::chunk_fn<boost::burl::detail::parser::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}>(boost::burl::detail::parser::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}&&) :283 74x 100.0% 100.0% boost::burl::detail::parser::chunk_fn::operator()(boost::capy::const_buffer, bool) const :296 106x 100.0% 100.0% boost::burl::detail::parser::parser(boost::burl::detail::parser::config const&, boost::http::detail::kind, boost::capy::any_read_stream) :302 192x 100.0% 100.0% 86.0% boost::burl::detail::parser::got_header() const :329 142x 100.0% 100.0% boost::burl::detail::parser::got_body() const :336 228x 100.0% 100.0% boost::burl::detail::parser::has_buffered_data() const :343 89x 100.0% 100.0% 100.0% boost::burl::detail::parser::raw_limit_rem() const :360 416x 100.0% 75.0% 86.0% boost::burl::detail::parser::dec_limit_rem() const :370 26x 100.0% 75.0% 86.0% boost::burl::detail::parser::payload_sized() const :380 1669x 100.0% 100.0% boost::burl::detail::parser::payload_rem() const :387 460x 100.0% 50.0% 86.0% boost::burl::detail::parser::table_reserve() const :395 658x 100.0% 100.0% boost::burl::detail::parser::start(bool) :403 208x 100.0% 80.0% 96.0% boost::burl::detail::parser::reset(boost::capy::any_read_stream) :439 50x 100.0% 100.0% boost::burl::detail::parser::refill() :466 987x 100.0% 100.0% 44.0% boost::burl::detail::parser::walk_chunks(boost::burl::detail::parser::chunk_fn, bool) :491 198x 100.0% 96.8% 93.0% boost::burl::detail::parser::flatten_chunks() :558 61x 85.7% 50.0% 85.0% boost::burl::detail::parser::flatten_chunks()::{lambda(std::error_code)#1}::operator()(std::error_code) const :570 61x 96.9% 92.9% 100.0% boost::burl::detail::parser::read_header() :618 498x 100.0% 100.0% 44.0% boost::burl::detail::parser::set_decoder(boost::burl::detail::parser::decoder*) :672 39x 100.0% 50.0% 67.0% boost::burl::detail::parser::set_body_limit(unsigned long) :680 4x 100.0% 100.0% boost::burl::detail::parser::read_body() :687 55x 100.0% 100.0% 44.0% boost::burl::detail::parser::get_response() const :768 299x 100.0% 50.0% 80.0% boost::burl::detail::parser::get_request() const :777 2x 100.0% 50.0% 80.0% boost::burl::detail::parser::decode_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>) :786 40x 100.0% 100.0% 44.0% boost::burl::detail::parser::decode_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}::operator()(boost::capy::const_buffer, bool) const :796 44x 100.0% 96.6% 90.0% boost::burl::detail::parser::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>) :885 143x 100.0% 100.0% 44.0% boost::burl::detail::parser::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)::{lambda(unsigned long)#1}::operator()(unsigned long) const :903 22x 100.0% 100.0% boost::burl::detail::parser::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}::operator()(boost::capy::const_buffer, bool) const :927 37x 100.0% 100.0% 94.0% boost::burl::detail::parser::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>) :1002 119x 100.0% 100.0% 44.0% boost::burl::detail::parser::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>)::{lambda(boost::capy::const_buffer, bool)#1}::operator()(boost::capy::const_buffer, bool) const :1035 32x 100.0% 86.7% 93.0% boost::burl::detail::parser::consume(unsigned long) :1096 86x 100.0% 100.0% 100.0% boost::burl::detail::parser::consume(unsigned long)::{lambda(boost::capy::const_buffer, bool)#1}::operator()(boost::capy::const_buffer, bool) const :1106 30x 100.0% 100.0% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Mohammad Nejati
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/burl
8 //
9
10 #include <boost/burl/detail/parser.hpp>
11
12 #include "util.hpp"
13
14 #include <boost/assert.hpp>
15 #include <boost/capy/buffers/buffer_slice.hpp>
16 #include <boost/capy/buffers/consuming_buffers.hpp>
17 #include <boost/capy/buffers/front.hpp>
18 #include <boost/capy/buffers/make_buffer.hpp>
19 #include <boost/capy/error.hpp>
20 #include <boost/url/grammar/error.hpp>
21 #include <boost/url/grammar/hexdig_chars.hpp>
22
23 #include <cstring>
24 #include <new>
25 #include <type_traits>
26 #include <utility>
27
28 namespace boost
29 {
30 namespace burl
31 {
32 namespace detail
33 {
34
35 using http::error::bad_payload;
36 using http::error::body_too_large;
37 using http::error::in_place_overflow;
38 using http::error::incomplete;
39 using http::error::need_data;
40 using http::condition::need_more_input;
41
42 using header = http::detail::header;
43 using payload = http::payload;
44
45 namespace
46 {
47
48 class chained_sequence
49 {
50 char const* pos_;
51 char const* end_;
52 char const* begin_b_;
53 char const* end_b_;
54
55 public:
56 198x chained_sequence(std::array<capy::const_buffer, 2> const& cbp)
57 198x : pos_(static_cast<char const*>(cbp[0].data()))
58 198x , end_(pos_ + cbp[0].size())
59 198x , begin_b_(static_cast<char const*>(cbp[1].data()))
60 198x , end_b_(begin_b_ + cbp[1].size())
61 {
62 198x }
63
64 61x chained_sequence(char const* p, std::size_t n)
65 61x : pos_(p)
66 61x , end_(p + n)
67 61x , begin_b_(end_)
68 61x , end_b_(end_)
69 {
70 61x }
71
72 char const*
73 1115x next() noexcept
74 {
75 1115x ++pos_;
76 // most frequently taken branch
77
2/2
✓ Branch 0 taken 996 times.
✓ Branch 1 taken 119 times.
1115x if(pos_ < end_)
78 996x return pos_;
79
80 // bring the second range
81
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 117 times.
119x if(begin_b_ != end_b_)
82 {
83 2x pos_ = begin_b_;
84 2x end_ = end_b_;
85 2x begin_b_ = end_b_;
86 2x return pos_;
87 }
88
89 // undo the increament
90 117x pos_ = end_;
91 117x return nullptr;
92 }
93
94 void
95 87x advance(std::size_t n) noexcept
96 {
97 87x auto const a = static_cast<std::size_t>(end_ - pos_);
98
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 48 times.
87x if(n < a)
99 {
100 39x pos_ += n;
101 39x return;
102 }
103 // exhaust the first range and continue into the second
104 48x n -= a;
105 48x pos_ = begin_b_ + n;
106 48x end_ = end_b_;
107 48x begin_b_ = end_b_;
108 }
109
110 bool
111 1030x empty() const noexcept
112 {
113 1030x return pos_ == end_;
114 }
115
116 char
117 1194x value() const noexcept
118 {
119 1194x return *pos_;
120 }
121
122 char const *
123 20x pos() const noexcept
124 {
125 20x return pos_;
126 }
127
128 std::size_t
129 606x size() const noexcept
130 {
131 606x return (end_ - pos_) + (end_b_ - begin_b_);
132 }
133
134 std::array<capy::const_buffer, 2>
135 170x prefix(std::size_t n) const noexcept
136 {
137 170x auto const a = static_cast<std::size_t>(end_ - pos_);
138 170x auto const b = static_cast<std::size_t>(end_b_ - begin_b_);
139
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 84 times.
170x auto const na = n < a ? n : a;
140 170x auto const nb = n - na < b ? n - na : b;
141 170x return { { { pos_, na }, { begin_b_, nb } } };
142 }
143 };
144
145 std::error_code
146 171x skip_to_eol(chained_sequence& cs) noexcept
147 {
148
2/2
✓ Branch 1 taken 597 times.
✓ Branch 2 taken 38 times.
635x while(!cs.empty())
149 {
150
2/2
✓ Branch 1 taken 133 times.
✓ Branch 2 taken 464 times.
597x if(cs.value() == '\r')
151 {
152
2/2
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 116 times.
133x if(!cs.next())
153 17x break;
154
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
116x if(cs.value() != '\n')
155 return bad_payload;
156 116x cs.next();
157 116x return {};
158 }
159 464x cs.next();
160 }
161 55x return need_data;
162 }
163
164 std::error_code
165 177x parse_chunk_header(
166 chained_sequence& cs,
167 std::uint64_t& size) noexcept
168 {
169 177x for(auto const start = cs.size();;)
170 {
171
2/2
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 298 times.
329x if(cs.empty())
172 31x return need_data;
173 298x auto const n = urls::grammar::hexdig_value(cs.value());
174
2/2
✓ Branch 0 taken 146 times.
✓ Branch 1 taken 152 times.
298x if(n < 0)
175 {
176
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 137 times.
146x if(start == cs.size())
177 9x return bad_payload;
178 // skip chunk header's exts
179 137x return skip_to_eol(cs);
180 }
181 // at least 4 significant bits are free
182
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 152 times.
152x if(size > (std::numeric_limits<std::uint64_t>::max)() >> 4)
183 return bad_payload;
184 152x size = (size << 4) | static_cast<std::uint64_t>(n);
185 152x cs.next();
186 152x }
187 }
188
189 std::error_code
190 58x skip_trailer(chained_sequence& cs) noexcept
191 {
192 for(;;)
193 {
194
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 60 times.
66x if(cs.empty())
195 6x return need_data;
196
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 34 times.
60x if(cs.value() == '\r')
197 {
198
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 22 times.
26x if(!cs.next())
199 4x return need_data;
200
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
22x if(cs.value() != '\n')
201 return bad_payload;
202 22x cs.next();
203 22x return {};
204 }
205 // skip to the end of the field
206
2/2
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 8 times.
34x if(auto ec = skip_to_eol(cs); ec)
207 26x return ec;
208 8x }
209 }
210
211 std::error_code
212 209x skip_crlf(chained_sequence& cs) noexcept
213 {
214
2/2
✓ Branch 1 taken 108 times.
✓ Branch 2 taken 101 times.
209x if(cs.size() < 2)
215 108x return need_data;
216
3/6
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 101 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 101 times.
101x if(cs.value() != '\r' || *cs.next() != '\n')
217 return bad_payload;
218 101x cs.next();
219 101x return {};
220 }
221
222 std::span<capy::const_buffer>
223 60x collect(
224 std::span<capy::const_buffer> dest,
225 std::array<capy::const_buffer, 2> const& src,
226 std::size_t at_most = std::size_t(-1)) noexcept
227 {
228 60x std::size_t n = 0;
229
1/2
✓ Branch 2 taken 120 times.
✗ Branch 3 not taken.
120x for(auto b : src)
230 {
231 120x auto const take = clamp(b.size(), at_most);
232
5/6
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
✓ Branch 5 taken 60 times.
✓ Branch 6 taken 60 times.
120x if(take == 0 || n == dest.size())
233 60x break;
234 60x at_most -= take;
235 60x dest[n++] = { b.data(), take };
236 }
237 60x return dest.first(n);
238 }
239
240 void
241 208x move_leftovers(
242 char* base,
243 std::array<capy::const_buffer, 2> const& bufs) noexcept
244 {
245 208x auto const* a = static_cast<char const*>(bufs[0].data());
246 208x auto an = bufs[0].size();
247 208x auto const* b = static_cast<char const*>(bufs[1].data());
248 208x auto const bn = bufs[1].size();
249
1/2
✓ Branch 0 taken 208 times.
✗ Branch 1 not taken.
208x if(bn == 0)
250 {
251 208x std::memmove(base, a, an);
252 208x return;
253 }
254 do
255 {
256 auto* bp = (std::min)(base + an, const_cast<char*>(a) - bn);
257 b = static_cast<char const*>(std::memmove(bp, b, bn));
258 auto chunk_a = static_cast<std::size_t>(b - base);
259 std::memcpy(base, a, chunk_a);
260 an -= chunk_a;
261 base += chunk_a;
262 a += chunk_a;
263 } while(an);
264 }
265
266 auto
267 25x prefix(
268 auto buf,
269 std::size_t n) noexcept -> decltype(buf)
270 {
271 25x return { buf.data(), clamp(buf.size(), n) };
272 };
273
274 } // namespace
275
276 struct parser::chunk_fn
277 {
278 void* obj_;
279 capy::io_result<std::size_t> (*invoke_)(
280 void*, capy::const_buffer, bool);
281
282 template<class F>
283 198x chunk_fn(F&& f) noexcept
284 198x : obj_(std::addressof(f))
285 198x , invoke_(
286 198x [](void* obj, capy::const_buffer b, bool last)
287 -> capy::io_result<std::size_t>
288 {
289 return (*static_cast<
290 std::remove_reference_t<F>*>(obj))(b, last);
291 })
292 {
293 198x }
294
295 capy::io_result<std::size_t>
296 106x operator()(capy::const_buffer b, bool last) const
297 {
298 106x return invoke_(obj_, b, last);
299 }
300 };
301
302 192x parser::
303 parser(
304 config const& cfg,
305 http::detail::kind kind,
306 192x capy::any_read_stream stream)
307 192x : hdr_limits_(cfg.hdr_limits)
308 192x , stream_(std::move(stream))
309 192x , body_limit_(cfg.body_limit)
310 {
311 192x constexpr auto align = alignof(header::entry);
312
1/1
✓ Branch 1 taken 192 times.
192x auto const h_cap = hdr_limits_.valid_space_needed() +
313 192x align * ((cfg.in_buffer + align - 1) / align);
314 384x auto* const p = static_cast<char*>(::operator new(
315
1/1
✓ Branch 1 taken 192 times.
192x sizeof(http::static_response) + h_cap + cfg.dec_buffer));
316 192x in_ = { p + sizeof(http::static_response), h_cap - table_reserve() };
317 192x out_ = { in_.ptr + h_cap, cfg.dec_buffer };
318
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 191 times.
192x if(kind == http::detail::kind::request)
319 1x h_.reset(reinterpret_cast<header*>(
320 1x ::new(static_cast<void*>(p))
321 1x http::static_request(in_.ptr, h_cap)));
322 else
323 191x h_.reset(reinterpret_cast<header*>(
324 191x ::new(static_cast<void*>(p))
325 191x http::static_response(in_.ptr, h_cap)));
326 192x }
327
328 bool
329 142x parser::
330 got_header() const noexcept
331 {
332 142x return got_header_;
333 }
334
335 bool
336 228x parser::
337 got_body() const noexcept
338 {
339 228x return got_body_;
340 }
341
342 bool
343 89x parser::
344 has_buffered_data() const noexcept
345 {
346
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 30 times.
89x switch(payload_)
347 {
348 12x case payload::chunked:
349 12x return in_.size() > chunk_rem_;
350 46x case payload::size:
351 46x return in_.size() > payload_rem();
352 1x case payload::to_eof:
353 1x return false;
354 30x default:
355 30x return !in_.empty();
356 }
357 }
358
359 std::size_t
360 416x parser::
361 raw_limit_rem() const noexcept
362 {
363
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 416 times.
416x BOOST_ASSERT(!dec_);
364
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 409 times.
416x if(body_limit_ <= transferred_)
365 7x return 0;
366 409x return clamp(body_limit_ - transferred_);
367 }
368
369 std::size_t
370 26x parser::
371 dec_limit_rem() const noexcept
372 {
373
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26x BOOST_ASSERT(dec_);
374
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 25 times.
26x if(body_limit_ <= decoded_)
375 1x return 0;
376 25x return clamp(body_limit_ - decoded_);
377 }
378
379 bool
380 1669x parser::
381 payload_sized() const noexcept
382 {
383 1669x return payload_ == payload::size;
384 }
385
386 std::size_t
387 460x parser::
388 payload_rem() const noexcept
389 {
390
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 460 times.
460x BOOST_ASSERT(payload_sized());
391 460x return clamp(h_->md.payload_size - transferred_);
392 }
393
394 std::size_t
395 658x parser::
396 table_reserve() const noexcept
397 {
398 658x return header::table_space(
399 658x hdr_limits_.max_fields);
400 }
401
402 void
403 208x parser::
404 start(bool head)
405 {
406
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 205 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
208x BOOST_ASSERT(!started_ || got_body_);
407
408
5/6
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 205 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 205 times.
208x if(payload_sized() && got_body_)
409 3x in_.consume(payload_rem());
410
411 auto* const base =
412 208x reinterpret_cast<char*>(h_.get())
413 208x + sizeof(http::static_response);
414 208x move_leftovers(base, in_.data());
415 416x in_ = { base, static_cast<std::size_t>(
416 208x out_.ptr - base) - table_reserve(), 0, in_.size() };
417 208x out_ = { out_.ptr, out_.cap };
418
419 208x *h_ = header{ http::detail::empty{ h_->kind } };
420 208x h_->buf = in_.ptr;
421 208x h_->cbuf = in_.ptr;
422 208x h_->cap = in_.cap + table_reserve();
423
424 208x dec_ = nullptr;
425 208x chunk_rem_ = 0;
426 208x transferred_ = 0;
427 208x decoded_ = 0;
428 208x dec_err_ = {};
429 208x payload_ = payload::none;
430 208x head_ = head;
431 208x started_ = true;
432 208x got_header_ = false;
433 208x got_body_ = false;
434 208x mid_chunk_ = false;
435 208x fin_chunk_ = false;
436 208x }
437
438 void
439 50x parser::
440 reset(capy::any_read_stream stream) noexcept
441 {
442 auto* const base =
443 50x reinterpret_cast<char*>(h_.get())
444 50x + sizeof(http::static_response);
445 100x in_ = { base, static_cast<std::size_t>(
446 50x out_.ptr - base) - table_reserve() };
447 50x out_ = { out_.ptr, out_.cap };
448
449 50x stream_ = std::move(stream);
450 50x dec_ = nullptr;
451 50x chunk_rem_ = 0;
452 50x transferred_ = 0;
453 50x decoded_ = 0;
454 50x dec_err_ = {};
455 50x payload_ = payload::none;
456 50x head_ = false;
457 50x started_ = false;
458 50x got_header_ = false;
459 50x got_body_ = false;
460 50x mid_chunk_ = false;
461 50x fin_chunk_ = false;
462 50x eof_ = false;
463 50x }
464
465 capy::io_task<>
466
1/1
✓ Branch 1 taken 987 times.
987x parser::refill()
467 {
468 if(eof_)
469 co_return { incomplete };
470 if(in_.full())
471 co_return { in_place_overflow };
472 auto [ec, n] = co_await stream_.read_some(in_.prepare());
473 in_.commit(n);
474 if(payload_sized() && payload_rem() <= in_.size())
475 got_body_ = true;
476 if(ec)
477 {
478 if(ec == capy::cond::eof)
479 {
480 eof_ = true;
481 if(payload_ == payload::to_eof)
482 got_body_ = true;
483 co_return {};
484 }
485 co_return ec;
486 }
487 co_return {};
488 1974x }
489
490 std::error_code
491 198x parser::
492 walk_chunks(chunk_fn f, bool dry)
493 {
494 198x chained_sequence cs = in_.data();
495 198x std::uint64_t size = chunk_rem_;
496
497
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 189 times.
198x if(fin_chunk_)
498 {
499 // from flatten_chunks
500 9x auto const b = in_.first(clamp(chunk_rem_));
501
1/1
✓ Branch 1 taken 9 times.
9x auto const [ec, n] = f(b, true);
502
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
9x if(!dry)
503 {
504 8x in_.consume(n);
505 8x chunk_rem_ -= n;
506 8x transferred_ += n;
507 }
508 9x return ec;
509 }
510
511
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 61 times.
189x if(mid_chunk_)
512 128x goto invoke;
513
514 61x loop:
515
2/2
✓ Branch 2 taken 51 times.
✓ Branch 3 taken 80 times.
131x if(auto ec = parse_chunk_header(cs, size); ec)
516 51x return ec;
517
518 // final chunk
519
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 42 times.
80x if(size == 0)
520 {
521
2/2
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 18 times.
38x if(auto ec = skip_trailer(cs); ec)
522 20x return ec;
523 18x got_body_ = true;
524
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
18x if(!dry)
525 {
526 12x fin_chunk_ = true;
527 12x in_.consume(in_.size() - cs.size());
528 }
529
1/1
✓ Branch 2 taken 18 times.
18x return f({}, true).ec;
530 }
531
532 42x invoke:
533
1/2
✓ Branch 3 taken 237 times.
✗ Branch 4 not taken.
237x for(const auto& b : cs.prefix(clamp(size)))
534 {
535
2/2
✓ Branch 1 taken 158 times.
✓ Branch 2 taken 79 times.
237x if(b.size() == 0)
536 158x break;
537
1/1
✓ Branch 1 taken 79 times.
79x auto const [ec, n] = f(b, false);
538 79x cs.advance(n);
539 79x size -= n;
540
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 25 times.
79x if(!dry)
541 {
542 54x in_.consume(in_.size() - cs.size());
543 54x chunk_rem_ = size;
544 54x transferred_ += n;
545 54x mid_chunk_ = true;
546 }
547
6/6
✓ Branch 1 taken 76 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 67 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 67 times.
79x if(ec || n < b.size())
548 12x return ec;
549 }
550
551
2/2
✓ Branch 2 taken 88 times.
✓ Branch 3 taken 70 times.
158x if(auto ec = skip_crlf(cs); ec)
552 88x return ec;
553
554 70x goto loop;
555 }
556
557 std::error_code
558 61x parser::
559 flatten_chunks()
560 {
561
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 61 times.
61x if(fin_chunk_)
562 return {};
563
564
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 61 times.
61x BOOST_ASSERT(in_.pos == 0);
565
566 61x std::size_t flat = clamp(chunk_rem_, in_.len);
567 61x char const* keep = in_.ptr + flat;
568 61x chained_sequence cs(keep, in_.len - flat);
569
570 61x auto bail = [&](std::error_code ec)
571 {
572 61x auto const tail = static_cast<std::size_t>(
573 61x in_.ptr + in_.len - keep);
574 61x std::memmove(in_.ptr + flat, keep, tail);
575 61x in_.len = flat + tail;
576 61x return ec;
577 61x };
578
579 for(;;)
580 {
581
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 15 times.
66x if(mid_chunk_)
582 {
583
2/2
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 31 times.
51x if(auto ec = skip_crlf(cs); ec)
584 20x return bail(ec);
585 }
586
587 46x std::uint64_t size = 0;
588
2/2
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 28 times.
46x if(auto ec = parse_chunk_header(cs, size); ec)
589 18x return bail(ec);
590
591
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 8 times.
28x if(size == 0)
592 {
593
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 4 times.
20x if(auto ec = skip_trailer(cs); ec)
594 16x return bail(ec);
595 4x got_body_ = true;
596 4x fin_chunk_ = true;
597 4x keep = cs.pos();
598 4x return bail({});
599 }
600
601
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8x if(size > in_.cap - flat)
602 return bail(in_place_overflow);
603
604 8x chunk_rem_ = flat + size;
605 8x mid_chunk_ = true;
606
607 8x auto const n = clamp(size, cs.size());
608 8x std::memmove(in_.ptr + flat, cs.pos(), n);
609 8x flat += n;
610 8x cs.advance(n);
611 8x keep = cs.pos();
612
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
8x if(n < size)
613 3x return bail(need_data);
614 5x }
615 }
616
617 capy::io_task<>
618
1/1
✓ Branch 1 taken 498 times.
498x parser::
619 read_header()
620 {
621 BOOST_ASSERT(started_);
622
623 if(got_header_)
624 co_return {};
625
626 for(;;)
627 {
628 system::error_code ec;
629 h_->parse(in_.size(), hdr_limits_, ec);
630 if(ec)
631 {
632 if(ec != need_more_input)
633 co_return { ec };
634 if(eof_ && in_.empty())
635 co_return { http::error::end_of_stream };
636 if(auto [rec] = co_await refill(); rec)
637 co_return rec;
638 continue;
639 }
640
641 // TODO: resize out_ based on payload and decoder
642 in_.ptr += h_->size;
643 in_.len -= h_->size;
644 in_.cap -= h_->size;
645
646 payload_ = head_ ? payload::none : h_->md.payload;
647 got_header_ = true;
648
649 switch(payload_)
650 {
651 case payload::error:
652 co_return { bad_payload };
653 case payload::none:
654 got_body_ = true;
655 break;
656 case payload::size:
657 if(payload_rem() <= in_.size())
658 got_body_ = true;
659 break;
660 case payload::chunked:
661 break;
662 case payload::to_eof:
663 if(eof_)
664 got_body_ = true;
665 break;
666 }
667 co_return {};
668 }
669 996x }
670
671 void
672 39x parser::
673 set_decoder(decoder* dec) noexcept
674 {
675
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39x BOOST_ASSERT(transferred_ == 0);
676 39x dec_ = dec;
677 39x }
678
679 void
680 4x parser::
681 set_body_limit(std::uint64_t n) noexcept
682 {
683 4x body_limit_ = n;
684 4x }
685
686 capy::io_task<std::string_view>
687
1/1
✓ Branch 1 taken 55 times.
55x parser::
688 read_body()
689 {
690 if(auto [ec] = co_await read_header(); ec)
691 co_return { ec, {} };
692
693 if(dec_)
694 {
695 if(decoded_ != out_.size())
696 co_return { incomplete, {} };
697 for(;;)
698 {
699 if(out_.full())
700 co_return { in_place_overflow, {} };
701 auto [ec, n] = co_await decode_some(out_.prepare());
702 out_.commit(n);
703 if(ec)
704 {
705 if(ec == capy::cond::eof)
706 co_return { {}, { out_.ptr, out_.len } };
707 co_return { ec, {} };
708 }
709 }
710 }
711
712 if(transferred_ != 0)
713 co_return { incomplete, {} };
714
715 switch(payload_)
716 {
717 case payload::error:
718 case payload::none:
719 {
720 co_return { {}, {} };
721 }
722 case payload::chunked:
723 {
724 for(;;)
725 {
726 if(chunk_rem_ > raw_limit_rem())
727 co_return { body_too_large, {} };
728 if(fin_chunk_)
729 co_return { {}, { in_.ptr, clamp(chunk_rem_) } };
730 if(auto ec = flatten_chunks(); ec)
731 {
732 if(ec != need_more_input)
733 co_return { ec, {} };
734 if(auto [fec] = co_await refill(); fec)
735 co_return { fec, {} };
736 }
737 }
738 }
739 case payload::size:
740 {
741 auto const rem = payload_rem();
742 if(rem > raw_limit_rem())
743 co_return { body_too_large, {} };
744 for(;;)
745 {
746 if(got_body_)
747 co_return { {}, { in_.ptr, clamp(in_.len, rem) } };
748 if(auto [fec] = co_await refill(); fec)
749 co_return { fec, {} };
750 }
751 }
752 case payload::to_eof:
753 {
754 for(;;)
755 {
756 if(in_.size() > raw_limit_rem())
757 co_return { body_too_large, {} };
758 if(got_body_)
759 co_return { {}, { in_.ptr, in_.len } };
760 if(auto [fec] = co_await refill(); fec)
761 co_return { fec, {} };
762 }
763 }
764 }
765 110x }
766
767 http::static_response const&
768 299x parser::
769 get_response() const
770 {
771
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 299 times.
299x BOOST_ASSERT(h_);
772 return reinterpret_cast<
773 299x http::static_response const&>(*h_);
774 }
775
776 http::static_request const&
777 2x parser::
778 get_request() const
779 {
780
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2x BOOST_ASSERT(h_);
781 return reinterpret_cast<
782 2x http::static_request const&>(*h_);
783 }
784
785 capy::io_task<std::size_t>
786
1/1
✓ Branch 1 taken 40 times.
40x parser::
787 decode_some(
788 std::span<capy::mutable_buffer const> buffers)
789 {
790 if(capy::buffer_empty(buffers))
791 co_return { {}, 0 };
792
793 auto outbufs = capy::consuming_buffers(buffers);
794 std::size_t prod = 0;
795 auto decode =
796 44x [&](capy::const_buffer in, bool last)
797 -> capy::io_result<std::size_t>
798 {
799
2/2
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 25 times.
44x if(dec_err_)
800 {
801
2/2
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 4 times.
19x if(dec_err_ == capy::cond::eof)
802 {
803
6/6
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 12 times.
15x if(!last || in.size() != 0)
804
1/1
✓ Branch 2 taken 3 times.
3x return { bad_payload, 0 };
805 }
806 16x return { dec_err_, 0 };
807 }
808 25x std::size_t cons = 0;
809 for(;;)
810 {
811 29x auto const out = capy::front(outbufs.data());
812
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 26 times.
29x if(out.size() == 0)
813
1/1
✓ Branch 2 taken 3 times.
3x return { {}, cons };
814 26x auto const lim = dec_limit_rem();
815
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 25 times.
26x if(lim == 0)
816
1/1
✓ Branch 2 taken 1 time.
1x return { body_too_large, cons };
817
1/1
✓ Branch 2 taken 25 times.
25x auto const r = dec_->process(
818 prefix(out, lim), in, last);
819 25x in += r.consumed;
820 25x cons += r.consumed;
821 25x transferred_ += r.consumed;
822 25x prod += r.produced;
823 25x decoded_ += r.produced;
824 25x outbufs.consume(r.produced);
825
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 9 times.
25x if(r.ec)
826 {
827 16x dec_err_ = r.ec;
828
1/1
✓ Branch 2 taken 16 times.
16x return { {}, cons };
829 }
830
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
9x if(r.produced == 0 && r.consumed == 0)
831 {
832 1x dec_err_ = error::decode_error;
833
1/1
✓ Branch 2 taken 1 time.
1x return { {}, cons };
834 }
835
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
8x if(in.size() == 0)
836
1/1
✓ Branch 2 taken 4 times.
4x return { {}, cons };
837 4x }
838 };
839
840 switch(payload_)
841 {
842 case payload::error:
843 case payload::none:
844 {
845 co_return { capy::error::eof, 0 };
846 }
847 case payload::chunked:
848 {
849 for(;;)
850 {
851 auto ec = walk_chunks(decode);
852 if(prod != 0)
853 co_return { {}, prod };
854 if(ec != need_more_input)
855 co_return { ec, 0 };
856 if(auto [fec] = co_await refill(); fec)
857 co_return { fec, 0 };
858 }
859 }
860 case payload::size:
861 case payload::to_eof:
862 {
863 for(;;)
864 {
865 auto const rem = payload_sized() ? payload_rem() : in_.size();
866 auto const in = in_.first(rem);
867 if(in.size() == 0 && !got_body_)
868 {
869 if(auto [fec] = co_await refill(); fec)
870 co_return { fec, 0 };
871 continue;
872 }
873 auto [ec, cons] = decode(in, got_body_ && in.size() == rem);
874 in_.consume(cons);
875 if(prod != 0)
876 co_return { {}, prod };
877 if(ec)
878 co_return { ec, 0 };
879 }
880 }
881 }
882 80x }
883
884 capy::io_task<std::size_t>
885
1/1
✓ Branch 1 taken 143 times.
143x parser::
886 do_read_some(
887 std::span<capy::mutable_buffer const> buffers)
888 {
889 if(auto [ec] = co_await read_header(); ec)
890 co_return { ec, 0 };
891
892 if(dec_)
893 {
894 if(!out_.empty())
895 {
896 auto const n = capy::buffer_copy(buffers, out_.data());
897 out_.consume(n);
898 co_return { {}, n };
899 }
900 co_return co_await decode_some(buffers);
901 }
902
903 22x auto copy = [&](std::size_t at_most)
904 {
905 22x auto const n = capy::buffer_copy(
906 22x buffers, in_.data(), at_most);
907 22x in_.consume(n);
908 22x transferred_ += n;
909 22x return n;
910 };
911
912 switch(payload_)
913 {
914 case payload::error:
915 case payload::none:
916 {
917 co_return { capy::error::eof, 0 };
918 }
919 case payload::chunked:
920 {
921 for(;;)
922 {
923 std::size_t read = 0;
924 std::size_t lim = raw_limit_rem();
925 auto outbufs = capy::consuming_buffers(buffers);
926 auto ec = walk_chunks(
927 37x [&](capy::const_buffer b, bool)
928 -> capy::io_result<std::size_t>
929 {
930 37x auto const take = clamp(b.size(), lim);
931 37x lim -= take;
932 37x auto const n = capy::buffer_copy(outbufs.data(), b, take);
933 37x read += n;
934 37x outbufs.consume(n);
935
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 35 times.
37x if(take < b.size())
936
1/1
✓ Branch 2 taken 2 times.
2x return { body_too_large, n };
937
1/1
✓ Branch 2 taken 35 times.
35x return { {}, n };
938
939 });
940 if(read != 0)
941 co_return { {}, read };
942 if(ec == need_more_input)
943 {
944 if(auto [fec] = co_await refill(); fec)
945 co_return { fec, 0 };
946 continue;
947 }
948 else if(ec)
949 co_return { ec, 0 };
950 BOOST_ASSERT(got_body_);
951 co_return { capy::error::eof, 0 };
952 }
953 }
954 case payload::size:
955 {
956 auto const rem = payload_rem();
957 if(rem == 0)
958 co_return { capy::error::eof, 0 };
959 auto const lim = raw_limit_rem();
960 if(lim == 0)
961 co_return { body_too_large, 0 };
962 if(!in_.empty())
963 co_return { {}, copy(clamp(rem, lim)) };
964 if(eof_)
965 co_return { incomplete, 0 };
966 auto [ec, n] = co_await stream_.read_some(
967 capy::buffer_slice(buffers, 0, clamp(rem, lim)));
968 transferred_ += n;
969 if(n == rem)
970 got_body_ = true;
971 if(ec == capy::cond::eof)
972 {
973 eof_ = true;
974 if(n != rem)
975 co_return { incomplete, n };
976 }
977 co_return { ec, n };
978 }
979 case payload::to_eof:
980 {
981 if(eof_)
982 co_return { capy::error::eof, 0 };
983 auto const lim = raw_limit_rem();
984 if(lim == 0)
985 co_return { body_too_large, 0 };
986 if(!in_.empty())
987 co_return { {}, copy(lim) };
988 auto [ec, n] = co_await stream_.read_some(
989 capy::buffer_slice(buffers, 0, lim));
990 transferred_ += n;
991 if(ec == capy::cond::eof)
992 {
993 eof_ = true;
994 got_body_ = true;
995 }
996 co_return { ec, n };
997 }
998 }
999 286x }
1000
1001 capy::io_task<std::span<capy::const_buffer>>
1002
1/1
✓ Branch 1 taken 119 times.
119x parser::
1003 pull(std::span<capy::const_buffer> dest)
1004 {
1005 if(auto [ec] = co_await read_header(); ec)
1006 co_return { ec, {} };
1007
1008 if(dec_)
1009 {
1010 if(!out_.empty())
1011 co_return { {}, collect(dest, out_.data()) };
1012 auto [ec, n] = co_await decode_some(out_.prepare());
1013 out_.commit(n);
1014 if(ec && n == 0)
1015 co_return { ec, {} };
1016 co_return { {}, collect(dest, out_.data()) };
1017 }
1018
1019 switch(payload_)
1020 {
1021 case payload::error:
1022 case payload::none:
1023 {
1024 co_return { capy::error::eof, {} };
1025 }
1026 case payload::chunked:
1027 {
1028 for(;;)
1029 {
1030 std::size_t n = 0;
1031 std::size_t lim = raw_limit_rem();
1032 if(lim == 0)
1033 co_return { body_too_large, {} };
1034 auto ec = walk_chunks(
1035 32x [&](capy::const_buffer b, bool last)
1036 -> capy::io_result<std::size_t>
1037 {
1038
5/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 25 times.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 25 times.
32x if(last && b.size() == 0)
1039
1/1
✓ Branch 2 taken 7 times.
7x return { capy::error::eof, 0 };
1040 25x auto const take = clamp(b.size(), lim);
1041
5/6
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 24 times.
25x if(take == 0 || n == dest.size())
1042
1/1
✓ Branch 2 taken 1 time.
1x return { {}, 0 };
1043 24x lim -= take;
1044 24x dest[n++] = { b.data(), take };
1045
1/1
✓ Branch 2 taken 24 times.
24x return { {}, take };
1046 },
1047 true);
1048 if(n != 0)
1049 co_return { {}, dest.first(n) };
1050 if(ec != need_more_input)
1051 {
1052 if(ec == capy::error::eof)
1053 consume(0); // chunk trailer
1054 co_return { ec, {} };
1055 }
1056 if(auto [fec] = co_await refill(); fec)
1057 co_return { fec, {} };
1058 }
1059 }
1060 case payload::size:
1061 {
1062 auto const rem = payload_rem();
1063 auto const lim = raw_limit_rem();
1064 if(rem == 0)
1065 co_return { capy::error::eof, {} };
1066 if(lim == 0)
1067 co_return { body_too_large, {} };
1068 for(;;)
1069 {
1070 if(!in_.empty())
1071 co_return { {}, collect(
1072 dest, in_.data(), clamp(rem, lim)) };
1073 if(auto [fec] = co_await refill(); fec)
1074 co_return { fec, {} };
1075 }
1076 }
1077 case payload::to_eof:
1078 {
1079 auto const lim = raw_limit_rem();
1080 if(lim == 0)
1081 co_return { body_too_large, {} };
1082 for(;;)
1083 {
1084 if(!in_.empty())
1085 co_return { {}, collect(dest, in_.data(), lim) };
1086 if(eof_)
1087 co_return { capy::error::eof, {} };
1088 if(auto [fec] = co_await refill(); fec)
1089 co_return { fec, {} };
1090 }
1091 }
1092 }
1093 238x }
1094
1095 void
1096 86x parser::
1097 consume(std::size_t n) noexcept
1098 {
1099
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 85 times.
86x if(dec_)
1100 1x return out_.consume(n);
1101
1102
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 59 times.
85x switch(payload_)
1103 {
1104 26x case payload::chunked:
1105 26x walk_chunks(
1106 26x [&](capy::const_buffer b, bool)
1107 -> capy::io_result<std::size_t>
1108 {
1109 30x auto const take = clamp(b.size(), n);
1110 30x n -= take;
1111
1/1
✓ Branch 2 taken 30 times.
30x return { {}, take };
1112 });
1113 26x return;
1114 59x default:
1115 59x in_.consume(n);
1116 59x transferred_ += n;
1117 59x return;
1118 }
1119 }
1120
1121 } // namespace detail
1122 } // namespace burl
1123 } // namespace boost
1124